1
1
/*
2
- * Copyright 2002-2018 the original author or authors.
2
+ * Copyright 2002-2019 the original author or authors.
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
21
21
import org .springframework .security .oauth2 .common .exceptions .RedirectMismatchException ;
22
22
import org .springframework .security .oauth2 .provider .ClientDetails ;
23
23
import org .springframework .util .Assert ;
24
+ import org .springframework .util .MultiValueMap ;
24
25
import org .springframework .util .StringUtils ;
25
26
import org .springframework .web .util .UriComponents ;
26
27
import org .springframework .web .util .UriComponentsBuilder ;
27
28
28
29
import java .util .Arrays ;
29
30
import java .util .Collection ;
30
31
import java .util .HashSet ;
32
+ import java .util .Iterator ;
33
+ import java .util .List ;
31
34
import java .util .Set ;
32
35
33
36
/**
@@ -47,7 +50,7 @@ public class DefaultRedirectResolver implements RedirectResolver {
47
50
/**
48
51
* Flag to indicate that requested URIs will match if they are a subdomain of the registered value.
49
52
*
50
- * @param matchSubdomains the flag value to set (deafult true)
53
+ * @param matchSubdomains the flag value to set (default true)
51
54
*/
52
55
public void setMatchSubdomains (boolean matchSubdomains ) {
53
56
this .matchSubdomains = matchSubdomains ;
@@ -105,7 +108,8 @@ private boolean containsRedirectGrantType(Set<String> grantTypes) {
105
108
/**
106
109
* Whether the requested redirect URI "matches" the specified redirect URI. For a URL, this implementation tests if
107
110
* the user requested redirect starts with the registered redirect, so it would have the same host and root path if
108
- * it is an HTTP URL. The port is also matched.
111
+ * it is an HTTP URL. The port, userinfo, query params also matched. Request redirect uri path can include
112
+ * additional parameters which are ignored for the match
109
113
* <p>
110
114
* For other (non-URL) cases, such as for some implicit clients, the redirect_uri must be an exact match.
111
115
*
@@ -115,22 +119,65 @@ private boolean containsRedirectGrantType(Set<String> grantTypes) {
115
119
*/
116
120
protected boolean redirectMatches (String requestedRedirect , String redirectUri ) {
117
121
UriComponents requestedRedirectUri = UriComponentsBuilder .fromUriString (requestedRedirect ).build ();
118
- String requestedRedirectUriScheme = (requestedRedirectUri .getScheme () != null ? requestedRedirectUri .getScheme () : "" );
119
- String requestedRedirectUriHost = (requestedRedirectUri .getHost () != null ? requestedRedirectUri .getHost () : "" );
120
- String requestedRedirectUriPath = (requestedRedirectUri .getPath () != null ? requestedRedirectUri .getPath () : "" );
121
-
122
122
UriComponents registeredRedirectUri = UriComponentsBuilder .fromUriString (redirectUri ).build ();
123
- String registeredRedirectUriScheme = (registeredRedirectUri .getScheme () != null ? registeredRedirectUri .getScheme () : "" );
124
- String registeredRedirectUriHost = (registeredRedirectUri .getHost () != null ? registeredRedirectUri .getHost () : "" );
125
- String registeredRedirectUriPath = (registeredRedirectUri .getPath () != null ? registeredRedirectUri .getPath () : "" );
126
123
127
- boolean portsMatch = this .matchPorts ? (registeredRedirectUri .getPort () == requestedRedirectUri .getPort ()) : true ;
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
+ * Checks whether the registered redirect uri query params key and values contains match the requested set
139
+ *
140
+ * The requested redirect uri query params are allowed to contain additional params which will be retained
141
+ *
142
+ * @param registeredRedirectUriQueryParams
143
+ * @param requestedRedirectUriQueryParams
144
+ * @return whether the params match
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
+
128
165
129
- return registeredRedirectUriScheme .equals (requestedRedirectUriScheme ) &&
130
- hostMatches (registeredRedirectUriHost , requestedRedirectUriHost ) &&
131
- portsMatch &&
132
- // Ensure exact path matching
133
- registeredRedirectUriPath .equals (StringUtils .cleanPath (requestedRedirectUriPath ));
166
+ /**
167
+ * Compares two strings but treats empty string or null equal
168
+ *
169
+ * @param str1
170
+ * @param str2
171
+ * @return true if strings are equal, false otherwise
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
+ }
134
181
}
135
182
136
183
/**
@@ -152,7 +199,7 @@ protected boolean hostMatches(String registered, String requested) {
152
199
*
153
200
* @param redirectUris the set of the registered URIs to try and find a match. This cannot be null or empty.
154
201
* @param requestedRedirect the URI used as part of the request
155
- * @return the matching URI
202
+ * @return redirect uri
156
203
* @throws RedirectMismatchException if no match was found
157
204
*/
158
205
private String obtainMatchingRedirect (Set <String > redirectUris , String requestedRedirect ) {
@@ -161,11 +208,26 @@ private String obtainMatchingRedirect(Set<String> redirectUris, String requested
161
208
if (redirectUris .size () == 1 && requestedRedirect == null ) {
162
209
return redirectUris .iterator ().next ();
163
210
}
211
+
164
212
for (String redirectUri : redirectUris ) {
165
213
if (requestedRedirect != null && redirectMatches (requestedRedirect , redirectUri )) {
166
- return requestedRedirect ;
214
+ // Initialize with the registered redirect-uri
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 ()); // retain additional params (if any)
226
+ redirectUriBuilder .fragment (null );
227
+ return redirectUriBuilder .build ().toUriString ();
167
228
}
168
229
}
230
+
169
231
throw new RedirectMismatchException ("Invalid redirect: " + requestedRedirect
170
232
+ " does not match one of the registered values: " + redirectUris .toString ());
171
233
}
0 commit comments