1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.springframework.security.oauth2.provider.endpoint;
17
18 import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
19 import org.springframework.security.oauth2.common.exceptions.InvalidRequestException;
20 import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
21 import org.springframework.security.oauth2.common.exceptions.RedirectMismatchException;
22 import org.springframework.security.oauth2.provider.ClientDetails;
23 import org.springframework.util.Assert;
24 import org.springframework.util.MultiValueMap;
25 import org.springframework.util.StringUtils;
26 import org.springframework.web.util.UriComponents;
27 import org.springframework.web.util.UriComponentsBuilder;
28
29 import java.util.Arrays;
30 import java.util.Collection;
31 import java.util.HashSet;
32 import java.util.Iterator;
33 import java.util.List;
34 import java.util.Set;
35
36
37
38
39
40
41
42 public class DefaultRedirectResolver implements RedirectResolver {
43
44 private Collection<String> redirectGrantTypes = Arrays.asList("implicit", "authorization_code");
45
46 private boolean matchSubdomains = false;
47
48 private boolean matchPorts = true;
49
50
51
52
53
54
55 public void setMatchSubdomains(boolean matchSubdomains) {
56 this.matchSubdomains = matchSubdomains;
57 }
58
59
60
61
62
63
64 public void setMatchPorts(boolean matchPorts) {
65 this.matchPorts = matchPorts;
66 }
67
68
69
70
71
72
73 public void setRedirectGrantTypes(Collection<String> redirectGrantTypes) {
74 this.redirectGrantTypes = new HashSet<String>(redirectGrantTypes);
75 }
76
77 public String resolveRedirect(String requestedRedirect, ClientDetails client) throws OAuth2Exception {
78
79 Set<String> authorizedGrantTypes = client.getAuthorizedGrantTypes();
80 if (authorizedGrantTypes.isEmpty()) {
81 throw new InvalidGrantException("A client must have at least one authorized grant type.");
82 }
83 if (!containsRedirectGrantType(authorizedGrantTypes)) {
84 throw new InvalidGrantException(
85 "A redirect_uri can only be used by implicit or authorization_code grant types.");
86 }
87
88 Set<String> registeredRedirectUris = client.getRegisteredRedirectUri();
89 if (registeredRedirectUris == null || registeredRedirectUris.isEmpty()) {
90 throw new InvalidRequestException("At least one redirect_uri must be registered with the client.");
91 }
92 return obtainMatchingRedirect(registeredRedirectUris, requestedRedirect);
93 }
94
95
96
97
98
99 private boolean containsRedirectGrantType(Set<String> grantTypes) {
100 for (String type : grantTypes) {
101 if (redirectGrantTypes.contains(type)) {
102 return true;
103 }
104 }
105 return false;
106 }
107
108
109
110
111
112
113
114
115
116
117
118
119
120 protected boolean redirectMatches(String requestedRedirect, String redirectUri) {
121 UriComponents requestedRedirectUri = UriComponentsBuilder.fromUriString(requestedRedirect).build();
122 UriComponents registeredRedirectUri = UriComponentsBuilder.fromUriString(redirectUri).build();
123
124 boolean schemeMatch = isEqual(registeredRedirectUri.getScheme(), requestedRedirectUri.getScheme());
125 boolean userInfoMatch = isEqual(registeredRedirectUri.getUserInfo(), requestedRedirectUri.getUserInfo());
126 boolean hostMatch = hostMatches(registeredRedirectUri.getHost(), requestedRedirectUri.getHost());
127 boolean portMatch = matchPorts ? registeredRedirectUri.getPort() == requestedRedirectUri.getPort() : true;
128 boolean pathMatch = isEqual(registeredRedirectUri.getPath(),
129 StringUtils.cleanPath(requestedRedirectUri.getPath()));
130 boolean queryParamMatch = matchQueryParams(registeredRedirectUri.getQueryParams(),
131 requestedRedirectUri.getQueryParams());
132
133 return schemeMatch && userInfoMatch && hostMatch && portMatch && pathMatch && queryParamMatch;
134 }
135
136
137
138
139
140
141
142
143
144
145
146 private boolean matchQueryParams(MultiValueMap<String, String> registeredRedirectUriQueryParams,
147 MultiValueMap<String, String> requestedRedirectUriQueryParams) {
148
149
150 Iterator<String> iter = registeredRedirectUriQueryParams.keySet().iterator();
151 while (iter.hasNext()) {
152 String key = iter.next();
153 List<String> registeredRedirectUriQueryParamsValues = registeredRedirectUriQueryParams.get(key);
154 List<String> requestedRedirectUriQueryParamsValues = requestedRedirectUriQueryParams.get(key);
155
156 if (!registeredRedirectUriQueryParamsValues.equals(requestedRedirectUriQueryParamsValues)) {
157 return false;
158 }
159 }
160
161 return true;
162 }
163
164
165
166
167
168
169
170
171
172
173 private boolean isEqual(String str1, String str2) {
174 if (StringUtils.isEmpty(str1) && StringUtils.isEmpty(str2)) {
175 return true;
176 } else if (!StringUtils.isEmpty(str1)) {
177 return str1.equals(str2);
178 } else {
179 return false;
180 }
181 }
182
183
184
185
186
187
188
189
190 protected boolean hostMatches(String registered, String requested) {
191 if (matchSubdomains) {
192 return isEqual(registered, requested) || (requested != null && requested.endsWith("." + registered));
193 }
194 return isEqual(registered, requested);
195 }
196
197
198
199
200
201
202
203
204
205 private String obtainMatchingRedirect(Set<String> redirectUris, String requestedRedirect) {
206 Assert.notEmpty(redirectUris, "Redirect URIs cannot be empty");
207
208 if (redirectUris.size() == 1 && requestedRedirect == null) {
209 return redirectUris.iterator().next();
210 }
211
212 for (String redirectUri : redirectUris) {
213 if (requestedRedirect != null && redirectMatches(requestedRedirect, redirectUri)) {
214
215 UriComponentsBuilder redirectUriBuilder = UriComponentsBuilder.fromUriString(redirectUri);
216
217 UriComponents requestedRedirectUri = UriComponentsBuilder.fromUriString(requestedRedirect).build();
218
219 if (this.matchSubdomains) {
220 redirectUriBuilder.host(requestedRedirectUri.getHost());
221 }
222 if (!this.matchPorts) {
223 redirectUriBuilder.port(requestedRedirectUri.getPort());
224 }
225 redirectUriBuilder.replaceQuery(requestedRedirectUri.getQuery());
226 redirectUriBuilder.fragment(null);
227 return redirectUriBuilder.build().toUriString();
228 }
229 }
230
231 throw new RedirectMismatchException("Invalid redirect: " + requestedRedirect
232 + " does not match one of the registered values.");
233 }
234 }