@ -16,14 +16,20 @@
@@ -16,14 +16,20 @@
package org.springframework.security.oauth2.server.resource.web.server.authentication ;
import static org.springframework.security.oauth2.server.resource.BearerTokenErrors.invalidRequest ;
import java.util.List ;
import java.util.regex.Matcher ;
import java.util.regex.Pattern ;
import reactor.core.publisher.Flux ;
import reactor.core.publisher.Mono ;
import reactor.util.function.Tuple2 ;
import reactor.util.function.Tuples ;
import org.springframework.http.HttpHeaders ;
import org.springframework.http.HttpMethod ;
import org.springframework.http.MediaType ;
import org.springframework.http.server.reactive.ServerHttpRequest ;
import org.springframework.security.core.Authentication ;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException ;
@ -47,16 +53,20 @@ import org.springframework.web.server.ServerWebExchange;
@@ -47,16 +53,20 @@ import org.springframework.web.server.ServerWebExchange;
* /
public class ServerBearerTokenAuthenticationConverter implements ServerAuthenticationConverter {
public static final String ACCESS_TOKEN_NAME = "access_token" ;
public static final String MULTIPLE_BEARER_TOKENS_ERROR_MSG = "Found multiple bearer tokens in the request" ;
private static final Pattern authorizationPattern = Pattern . compile ( "^Bearer (?<token>[a-zA-Z0-9-._~+/]+=*)$" ,
Pattern . CASE_INSENSITIVE ) ;
private boolean allowUriQueryParameter = false ;
private boolean allowFormEncodedBodyParameter = false ;
private String bearerTokenHeaderName = HttpHeaders . AUTHORIZATION ;
@Override
public Mono < Authentication > convert ( ServerWebExchange exchange ) {
return Mono . fromCallable ( ( ) - > token ( exchange . getRequest ( ) ) ) . map ( ( token ) - > {
return Mono . de fe r( ( ) - > token ( exchange ) ) . map ( token - > {
if ( token . isEmpty ( ) ) {
BearerTokenError error = invalidTokenError ( ) ;
throw new OAuth2AuthenticationException ( error ) ;
@ -65,43 +75,53 @@ public class ServerBearerTokenAuthenticationConverter implements ServerAuthentic
@@ -65,43 +75,53 @@ public class ServerBearerTokenAuthenticationConverter implements ServerAuthentic
} ) ;
}
private String token ( ServerHttpRequest request ) {
String authorizationHeaderToken = resolveFromAuthorizationHeader ( request . getHeaders ( ) ) ;
String parameterToken = resolveAccessTokenFromRequest ( request ) ;
if ( authorizationHeaderToken ! = null ) {
if ( parameterToken ! = null ) {
BearerTokenError error = BearerTokenErrors
. invalidRequest ( "Found multiple bearer tokens in the request" ) ;
throw new OAuth2AuthenticationException ( error ) ;
}
return authorizationHeaderToken ;
}
if ( parameterToken ! = null & & ! StringUtils . hasText ( parameterToken ) ) {
BearerTokenError error = BearerTokenErrors
. invalidRequest ( "The requested token parameter is an empty string" ) ;
throw new OAuth2AuthenticationException ( error ) ;
}
return parameterToken ;
private Mono < String > token ( ServerWebExchange exchange ) {
final ServerHttpRequest request = exchange . getRequest ( ) ;
return Flux . merge ( resolveFromAuthorizationHeader ( request . getHeaders ( ) ) . map ( s - > Tuples . of ( s , TokenSource . HEADER ) ) ,
resolveAccessTokenFromRequest ( request ) . map ( s - > Tuples . of ( s , TokenSource . QUERY_PARAMETER ) ) ,
resolveAccessTokenFromBody ( exchange ) . map ( s - > Tuples . of ( s , TokenSource . BODY_PARAMETER ) ) )
. collectList ( )
. mapNotNull ( tokenTuples - > {
switch ( tokenTuples . size ( ) ) {
case 0 :
return null ;
case 1 :
return getTokenIfSupported ( tokenTuples . get ( 0 ) , request ) ;
default :
BearerTokenError error = invalidRequest ( MULTIPLE_BEARER_TOKENS_ERROR_MSG ) ;
throw new OAuth2AuthenticationException ( error ) ;
}
} ) ;
}
private String resolveAccessTokenFromRequest ( ServerHttpRequest request ) {
if ( ! isParameterTokenSupportedForRequest ( request ) ) {
return null ;
}
List < String > parameterTokens = request . getQueryParams ( ) . get ( "access_token" ) ;
private static Mono < String > resolveAccessTokenFromRequest ( ServerHttpRequest request ) {
List < String > parameterTokens = request . getQueryParams ( ) . get ( ACCESS_TOKEN_NAME ) ;
if ( CollectionUtils . isEmpty ( parameterTokens ) ) {
return null ;
return Mono . empty ( ) ;
}
if ( parameterTokens . size ( ) = = 1 ) {
return parameterTokens . get ( 0 ) ;
return Mono . just ( parameterTokens . get ( 0 ) ) ;
}
BearerTokenError error = BearerTokenErrors . invalidRequest ( "Found multiple bearer tokens in the request" ) ;
BearerTokenError error = invalidRequest ( MULTIPLE_BEARER_TOKENS_ERROR_MSG ) ;
throw new OAuth2AuthenticationException ( error ) ;
}
private String getTokenIfSupported ( Tuple2 < String , TokenSource > tokenTuple , ServerHttpRequest request ) {
switch ( tokenTuple . getT2 ( ) ) {
case HEADER :
return tokenTuple . getT1 ( ) ;
case QUERY_PARAMETER :
return isParameterTokenSupportedForRequest ( request ) ? tokenTuple . getT1 ( ) : null ;
case BODY_PARAMETER :
return isBodyParameterTokenSupportedForRequest ( request ) ? tokenTuple . getT1 ( ) : null ;
default :
throw new IllegalArgumentException ( ) ;
}
}
/ * *
* Set if transport of access token using URI query parameter is supported . Defaults
* to { @code false } .
@ -127,25 +147,70 @@ public class ServerBearerTokenAuthenticationConverter implements ServerAuthentic
@@ -127,25 +147,70 @@ public class ServerBearerTokenAuthenticationConverter implements ServerAuthentic
this . bearerTokenHeaderName = bearerTokenHeaderName ;
}
private String resolveFromAuthorizationHeader ( HttpHeaders headers ) {
/ * *
* Set if transport of access token using form - encoded body parameter is supported .
* Defaults to { @code false } .
* @param allowFormEncodedBodyParameter if the form - encoded body parameter is
* supported
* @since 6 . 5
* /
public void setAllowFormEncodedBodyParameter ( boolean allowFormEncodedBodyParameter ) {
this . allowFormEncodedBodyParameter = allowFormEncodedBodyParameter ;
}
private Mono < String > resolveFromAuthorizationHeader ( HttpHeaders headers ) {
String authorization = headers . getFirst ( this . bearerTokenHeaderName ) ;
if ( ! StringUtils . startsWithIgnoreCase ( authorization , "bearer" ) ) {
return null ;
return Mono . empty ( ) ;
}
Matcher matcher = authorizationPattern . matcher ( authorization ) ;
if ( ! matcher . matches ( ) ) {
BearerTokenError error = invalidTokenError ( ) ;
throw new OAuth2AuthenticationException ( error ) ;
}
return matcher . group ( "token" ) ;
return Mono . just ( matcher . group ( "token" ) ) ;
}
private static BearerTokenError invalidTokenError ( ) {
return BearerTokenErrors . invalidToken ( "Bearer token is malformed" ) ;
}
private Mono < String > resolveAccessTokenFromBody ( ServerWebExchange exchange ) {
if ( ! allowFormEncodedBodyParameter ) {
return Mono . empty ( ) ;
}
final ServerHttpRequest request = exchange . getRequest ( ) ;
if ( request . getMethod ( ) = = HttpMethod . POST & &
MediaType . APPLICATION_FORM_URLENCODED . equalsTypeAndSubtype ( request . getHeaders ( ) . getContentType ( ) ) ) {
return exchange . getFormData ( ) . mapNotNull ( formData - > {
if ( formData . isEmpty ( ) ) {
return null ;
}
final List < String > tokens = formData . get ( ACCESS_TOKEN_NAME ) ;
if ( tokens = = null ) {
return null ;
}
if ( tokens . size ( ) > 1 ) {
BearerTokenError error = invalidRequest ( MULTIPLE_BEARER_TOKENS_ERROR_MSG ) ;
throw new OAuth2AuthenticationException ( error ) ;
}
return formData . getFirst ( ACCESS_TOKEN_NAME ) ;
} ) ;
}
return Mono . empty ( ) ;
}
private boolean isBodyParameterTokenSupportedForRequest ( ServerHttpRequest request ) {
return this . allowFormEncodedBodyParameter & & HttpMethod . POST = = request . getMethod ( ) ;
}
private boolean isParameterTokenSupportedForRequest ( ServerHttpRequest request ) {
return this . allowUriQueryParameter & & HttpMethod . GET . equals ( request . getMethod ( ) ) ;
}
private enum TokenSource { HEADER , QUERY_PARAMETER , BODY_PARAMETER }
}