16
16
17
17
package org .springframework .security .oauth2 .server .resource .web .server .authentication ;
18
18
19
+ import static org .springframework .security .oauth2 .server .resource .BearerTokenErrors .invalidRequest ;
20
+
19
21
import java .util .List ;
20
22
import java .util .regex .Matcher ;
21
23
import java .util .regex .Pattern ;
22
24
25
+ import reactor .core .publisher .Flux ;
23
26
import reactor .core .publisher .Mono ;
27
+ import reactor .util .function .Tuple2 ;
28
+ import reactor .util .function .Tuples ;
24
29
25
30
import org .springframework .http .HttpHeaders ;
26
31
import org .springframework .http .HttpMethod ;
32
+ import org .springframework .http .MediaType ;
27
33
import org .springframework .http .server .reactive .ServerHttpRequest ;
28
34
import org .springframework .security .core .Authentication ;
29
35
import org .springframework .security .oauth2 .core .OAuth2AuthenticationException ;
47
53
*/
48
54
public class ServerBearerTokenAuthenticationConverter implements ServerAuthenticationConverter {
49
55
56
+ public static final String ACCESS_TOKEN_NAME = "access_token" ;
57
+ public static final String MULTIPLE_BEARER_TOKENS_ERROR_MSG = "Found multiple bearer tokens in the request" ;
50
58
private static final Pattern authorizationPattern = Pattern .compile ("^Bearer (?<token>[a-zA-Z0-9-._~+/]+=*)$" ,
51
59
Pattern .CASE_INSENSITIVE );
52
60
53
61
private boolean allowUriQueryParameter = false ;
54
62
63
+ private boolean allowFormEncodedBodyParameter = false ;
64
+
55
65
private String bearerTokenHeaderName = HttpHeaders .AUTHORIZATION ;
56
66
57
67
@ Override
58
68
public Mono <Authentication > convert (ServerWebExchange exchange ) {
59
- return Mono .fromCallable (() -> token (exchange . getRequest ())) .map (( token ) -> {
69
+ return Mono .defer (() -> token (exchange )) .map (token -> {
60
70
if (token .isEmpty ()) {
61
71
BearerTokenError error = invalidTokenError ();
62
72
throw new OAuth2AuthenticationException (error );
@@ -65,43 +75,53 @@ public Mono<Authentication> convert(ServerWebExchange exchange) {
65
75
});
66
76
}
67
77
68
- private String token (ServerHttpRequest request ) {
69
- String authorizationHeaderToken = resolveFromAuthorizationHeader ( request . getHeaders () );
70
- String parameterToken = resolveAccessTokenFromRequest ( request );
71
-
72
- if ( authorizationHeaderToken != null ) {
73
- if ( parameterToken != null ) {
74
- BearerTokenError error = BearerTokenErrors
75
- . invalidRequest ( "Found multiple bearer tokens in the request" );
76
- throw new OAuth2AuthenticationException ( error );
77
- }
78
- return authorizationHeaderToken ;
79
- }
80
- if ( parameterToken != null && ! StringUtils . hasText ( parameterToken )) {
81
- BearerTokenError error = BearerTokenErrors
82
- . invalidRequest ( "The requested token parameter is an empty string" );
83
- throw new OAuth2AuthenticationException (error );
84
- }
85
- return parameterToken ;
78
+ private Mono < String > token (ServerWebExchange exchange ) {
79
+ final ServerHttpRequest request = exchange . getRequest ( );
80
+
81
+ return Flux . merge ( resolveFromAuthorizationHeader ( request . getHeaders ()). map ( s -> Tuples . of ( s , TokenSource . HEADER )),
82
+ resolveAccessTokenFromRequest ( request ). map ( s -> Tuples . of ( s , TokenSource . QUERY_PARAMETER )),
83
+ resolveAccessTokenFromBody ( exchange ). map ( s -> Tuples . of ( s , TokenSource . BODY_PARAMETER )))
84
+ . collectList ()
85
+ . mapNotNull ( tokenTuples -> {
86
+ switch ( tokenTuples . size ()) {
87
+ case 0 :
88
+ return null ;
89
+ case 1 :
90
+ return getTokenIfSupported ( tokenTuples . get ( 0 ), request );
91
+ default :
92
+ BearerTokenError error = invalidRequest ( MULTIPLE_BEARER_TOKENS_ERROR_MSG );
93
+ throw new OAuth2AuthenticationException (error );
94
+ }
95
+ }) ;
86
96
}
87
97
88
- private String resolveAccessTokenFromRequest (ServerHttpRequest request ) {
89
- if (!isParameterTokenSupportedForRequest (request )) {
90
- return null ;
91
- }
92
- List <String > parameterTokens = request .getQueryParams ().get ("access_token" );
98
+ private static Mono <String > resolveAccessTokenFromRequest (ServerHttpRequest request ) {
99
+ List <String > parameterTokens = request .getQueryParams ().get (ACCESS_TOKEN_NAME );
93
100
if (CollectionUtils .isEmpty (parameterTokens )) {
94
- return null ;
101
+ return Mono . empty () ;
95
102
}
96
103
if (parameterTokens .size () == 1 ) {
97
- return parameterTokens .get (0 );
104
+ return Mono . just ( parameterTokens .get (0 ) );
98
105
}
99
106
100
- BearerTokenError error = BearerTokenErrors . invalidRequest ("Found multiple bearer tokens in the request" );
107
+ BearerTokenError error = invalidRequest (MULTIPLE_BEARER_TOKENS_ERROR_MSG );
101
108
throw new OAuth2AuthenticationException (error );
102
109
103
110
}
104
111
112
+ private String getTokenIfSupported (Tuple2 <String , TokenSource > tokenTuple , ServerHttpRequest request ) {
113
+ switch (tokenTuple .getT2 ()) {
114
+ case HEADER :
115
+ return tokenTuple .getT1 ();
116
+ case QUERY_PARAMETER :
117
+ return isParameterTokenSupportedForRequest (request ) ? tokenTuple .getT1 () : null ;
118
+ case BODY_PARAMETER :
119
+ return isBodyParameterTokenSupportedForRequest (request ) ? tokenTuple .getT1 () : null ;
120
+ default :
121
+ throw new IllegalArgumentException ();
122
+ }
123
+ }
124
+
105
125
/**
106
126
* Set if transport of access token using URI query parameter is supported. Defaults
107
127
* to {@code false}.
@@ -127,25 +147,70 @@ public void setBearerTokenHeaderName(String bearerTokenHeaderName) {
127
147
this .bearerTokenHeaderName = bearerTokenHeaderName ;
128
148
}
129
149
130
- private String resolveFromAuthorizationHeader (HttpHeaders headers ) {
150
+ /**
151
+ * Set if transport of access token using form-encoded body parameter is supported.
152
+ * Defaults to {@code false}.
153
+ * @param allowFormEncodedBodyParameter if the form-encoded body parameter is
154
+ * supported
155
+ * @since 6.5
156
+ */
157
+ public void setAllowFormEncodedBodyParameter (boolean allowFormEncodedBodyParameter ) {
158
+ this .allowFormEncodedBodyParameter = allowFormEncodedBodyParameter ;
159
+ }
160
+
161
+ private Mono <String > resolveFromAuthorizationHeader (HttpHeaders headers ) {
131
162
String authorization = headers .getFirst (this .bearerTokenHeaderName );
132
163
if (!StringUtils .startsWithIgnoreCase (authorization , "bearer" )) {
133
- return null ;
164
+ return Mono . empty () ;
134
165
}
135
166
Matcher matcher = authorizationPattern .matcher (authorization );
136
167
if (!matcher .matches ()) {
137
168
BearerTokenError error = invalidTokenError ();
138
169
throw new OAuth2AuthenticationException (error );
139
170
}
140
- return matcher .group ("token" );
171
+ return Mono . just ( matcher .group ("token" ) );
141
172
}
142
173
143
174
private static BearerTokenError invalidTokenError () {
144
175
return BearerTokenErrors .invalidToken ("Bearer token is malformed" );
145
176
}
146
177
178
+ private Mono <String > resolveAccessTokenFromBody (ServerWebExchange exchange ) {
179
+ if (!allowFormEncodedBodyParameter ) {
180
+ return Mono .empty ();
181
+ }
182
+
183
+ final ServerHttpRequest request = exchange .getRequest ();
184
+
185
+ if (request .getMethod () == HttpMethod .POST &&
186
+ MediaType .APPLICATION_FORM_URLENCODED .equalsTypeAndSubtype (request .getHeaders ().getContentType ())) {
187
+
188
+ return exchange .getFormData ().mapNotNull (formData -> {
189
+ if (formData .isEmpty ()) {
190
+ return null ;
191
+ }
192
+ final List <String > tokens = formData .get (ACCESS_TOKEN_NAME );
193
+ if (tokens == null ) {
194
+ return null ;
195
+ }
196
+ if (tokens .size () > 1 ) {
197
+ BearerTokenError error = invalidRequest (MULTIPLE_BEARER_TOKENS_ERROR_MSG );
198
+ throw new OAuth2AuthenticationException (error );
199
+ }
200
+ return formData .getFirst (ACCESS_TOKEN_NAME );
201
+ });
202
+ }
203
+ return Mono .empty ();
204
+ }
205
+
206
+ private boolean isBodyParameterTokenSupportedForRequest (ServerHttpRequest request ) {
207
+ return this .allowFormEncodedBodyParameter && HttpMethod .POST == request .getMethod ();
208
+ }
209
+
147
210
private boolean isParameterTokenSupportedForRequest (ServerHttpRequest request ) {
148
211
return this .allowUriQueryParameter && HttpMethod .GET .equals (request .getMethod ());
149
212
}
150
213
214
+ private enum TokenSource {HEADER , QUERY_PARAMETER , BODY_PARAMETER }
215
+
151
216
}
0 commit comments