11/*
2- * Copyright 2002-2021 the original author or authors.
2+ * Copyright 2002-2024 the original author or authors.
33 *
44 * Licensed under the Apache License, Version 2.0 (the "License");
55 * you may not use this file except in compliance with the License.
1616
1717package org .springframework .security .oauth2 .server .resource .web .server .authentication ;
1818
19+ import static org .springframework .security .oauth2 .server .resource .BearerTokenErrors .invalidRequest ;
20+
1921import java .util .List ;
2022import java .util .regex .Matcher ;
2123import java .util .regex .Pattern ;
2224
25+ import reactor .core .publisher .Flux ;
2326import reactor .core .publisher .Mono ;
27+ import reactor .util .function .Tuple2 ;
28+ import reactor .util .function .Tuples ;
2429
2530import org .springframework .http .HttpHeaders ;
2631import org .springframework .http .HttpMethod ;
32+ import org .springframework .http .MediaType ;
2733import org .springframework .http .server .reactive .ServerHttpRequest ;
2834import org .springframework .security .core .Authentication ;
2935import org .springframework .security .oauth2 .core .OAuth2AuthenticationException ;
4753 */
4854public class ServerBearerTokenAuthenticationConverter implements ServerAuthenticationConverter {
4955
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" ;
5058 private static final Pattern authorizationPattern = Pattern .compile ("^Bearer (?<token>[a-zA-Z0-9-._~+/]+=*)$" ,
5159 Pattern .CASE_INSENSITIVE );
5260
5361 private boolean allowUriQueryParameter = false ;
5462
63+ private boolean allowFormEncodedBodyParameter = false ;
64+
5565 private String bearerTokenHeaderName = HttpHeaders .AUTHORIZATION ;
5666
5767 @ Override
5868 public Mono <Authentication > convert (ServerWebExchange exchange ) {
59- return Mono .fromCallable (() -> token (exchange . getRequest ())) .map (( token ) -> {
69+ return Mono .defer (() -> token (exchange )) .map (token -> {
6070 if (token .isEmpty ()) {
6171 BearerTokenError error = invalidTokenError ();
6272 throw new OAuth2AuthenticationException (error );
@@ -65,38 +75,45 @@ public Mono<Authentication> convert(ServerWebExchange exchange) {
6575 });
6676 }
6777
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 && isParameterTokenSupportedForRequest (request )) {
81- return parameterToken ;
82- }
83- return null ;
78+ private Mono <String > token (ServerWebExchange exchange ) {
79+ final var 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 -> switch (tokenTuples .size ()) {
86+ case 0 -> null ;
87+ case 1 -> getTokenIfSupported (tokenTuples .get (0 ), request );
88+ default -> {
89+ BearerTokenError error = invalidRequest (MULTIPLE_BEARER_TOKENS_ERROR_MSG );
90+ throw new OAuth2AuthenticationException (error );
91+ }
92+ });
8493 }
8594
86- private static String resolveAccessTokenFromRequest (ServerHttpRequest request ) {
87- List <String > parameterTokens = request .getQueryParams ().get ("access_token" );
95+ private static Mono < String > resolveAccessTokenFromRequest (ServerHttpRequest request ) {
96+ List <String > parameterTokens = request .getQueryParams ().get (ACCESS_TOKEN_NAME );
8897 if (CollectionUtils .isEmpty (parameterTokens )) {
89- return null ;
98+ return Mono . empty () ;
9099 }
91100 if (parameterTokens .size () == 1 ) {
92- return parameterTokens .get (0 );
101+ return Mono . just ( parameterTokens .get (0 ) );
93102 }
94103
95- BearerTokenError error = BearerTokenErrors . invalidRequest ("Found multiple bearer tokens in the request" );
104+ BearerTokenError error = invalidRequest (MULTIPLE_BEARER_TOKENS_ERROR_MSG );
96105 throw new OAuth2AuthenticationException (error );
97106
98107 }
99108
109+ private String getTokenIfSupported (Tuple2 <String , TokenSource > tokenTuple , ServerHttpRequest request ) {
110+ return switch (tokenTuple .getT2 ()) {
111+ case HEADER -> tokenTuple .getT1 ();
112+ case QUERY_PARAMETER -> isParameterTokenSupportedForRequest (request ) ? tokenTuple .getT1 () : null ;
113+ case BODY_PARAMETER -> isBodyParameterTokenSupportedForRequest (request ) ? tokenTuple .getT1 () : null ;
114+ };
115+ }
116+
100117 /**
101118 * Set if transport of access token using URI query parameter is supported. Defaults
102119 * to {@code false}.
@@ -122,25 +139,73 @@ public void setBearerTokenHeaderName(String bearerTokenHeaderName) {
122139 this .bearerTokenHeaderName = bearerTokenHeaderName ;
123140 }
124141
125- private String resolveFromAuthorizationHeader (HttpHeaders headers ) {
142+ /**
143+ * Set if transport of access token using form-encoded body parameter is supported.
144+ * Defaults to {@code false}.
145+ * @param allowFormEncodedBodyParameter if the form-encoded body parameter is
146+ * supported
147+ */
148+ public void setAllowFormEncodedBodyParameter (boolean allowFormEncodedBodyParameter ) {
149+ this .allowFormEncodedBodyParameter = allowFormEncodedBodyParameter ;
150+ }
151+
152+ private Mono <String > resolveFromAuthorizationHeader (HttpHeaders headers ) {
126153 String authorization = headers .getFirst (this .bearerTokenHeaderName );
127154 if (!StringUtils .startsWithIgnoreCase (authorization , "bearer" )) {
128- return null ;
155+ return Mono . empty () ;
129156 }
130157 Matcher matcher = authorizationPattern .matcher (authorization );
131158 if (!matcher .matches ()) {
132159 BearerTokenError error = invalidTokenError ();
133160 throw new OAuth2AuthenticationException (error );
134161 }
135- return matcher .group ("token" );
162+ return Mono . just ( matcher .group ("token" ) );
136163 }
137164
138165 private static BearerTokenError invalidTokenError () {
139166 return BearerTokenErrors .invalidToken ("Bearer token is malformed" );
140167 }
141168
169+ private Mono <String > resolveAccessTokenFromBody (ServerWebExchange exchange ) {
170+ if (!allowFormEncodedBodyParameter ) {
171+ return Mono .empty ();
172+ }
173+
174+ final var request = exchange .getRequest ();
175+
176+ if (request .getMethod () == HttpMethod .POST &&
177+ MediaType .APPLICATION_FORM_URLENCODED .equalsTypeAndSubtype (request .getHeaders ().getContentType ())) {
178+
179+ return exchange .getFormData ().mapNotNull (formData -> {
180+ if (formData .isEmpty ()) {
181+ return null ;
182+ }
183+ if (formData .size () > 1 ) {
184+ var error = invalidRequest ("The HTTP request entity-body is not single-part" );
185+ throw new OAuth2AuthenticationException (error );
186+ }
187+ final var tokens = formData .get (ACCESS_TOKEN_NAME );
188+ if (tokens == null ) {
189+ return null ;
190+ }
191+ if (tokens .size () > 1 ) {
192+ var error = invalidRequest (MULTIPLE_BEARER_TOKENS_ERROR_MSG );
193+ throw new OAuth2AuthenticationException (error );
194+ }
195+ return formData .getFirst (ACCESS_TOKEN_NAME );
196+ });
197+ }
198+ return Mono .empty ();
199+ }
200+
201+ private boolean isBodyParameterTokenSupportedForRequest (ServerHttpRequest request ) {
202+ return this .allowFormEncodedBodyParameter && HttpMethod .POST == request .getMethod ();
203+ }
204+
142205 private boolean isParameterTokenSupportedForRequest (ServerHttpRequest request ) {
143206 return this .allowUriQueryParameter && HttpMethod .GET .equals (request .getMethod ());
144207 }
145208
209+ private enum TokenSource {HEADER , QUERY_PARAMETER , BODY_PARAMETER }
210+
146211}
0 commit comments