Skip to content

Commit 1fb3fc8

Browse files
committed
Polish gh-15819
Closes gh-15818
1 parent 9674532 commit 1fb3fc8

File tree

2 files changed

+145
-175
lines changed

2 files changed

+145
-175
lines changed

oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/server/authentication/ServerBearerTokenAuthenticationConverter.java

Lines changed: 62 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,12 @@
1616

1717
package org.springframework.security.oauth2.server.resource.web.server.authentication;
1818

19-
import static org.springframework.security.oauth2.server.resource.BearerTokenErrors.invalidRequest;
20-
2119
import java.util.List;
2220
import java.util.regex.Matcher;
2321
import java.util.regex.Pattern;
2422

2523
import reactor.core.publisher.Flux;
2624
import reactor.core.publisher.Mono;
27-
import reactor.util.function.Tuple2;
28-
import reactor.util.function.Tuples;
2925

3026
import org.springframework.http.HttpHeaders;
3127
import org.springframework.http.HttpMethod;
@@ -38,6 +34,7 @@
3834
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken;
3935
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
4036
import org.springframework.util.CollectionUtils;
37+
import org.springframework.util.MultiValueMap;
4138
import org.springframework.util.StringUtils;
4239
import org.springframework.web.server.ServerWebExchange;
4340

@@ -53,73 +50,89 @@
5350
*/
5451
public class ServerBearerTokenAuthenticationConverter implements ServerAuthenticationConverter {
5552

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";
53+
private static final String ACCESS_TOKEN_PARAMETER_NAME = "access_token";
54+
5855
private static final Pattern authorizationPattern = Pattern.compile("^Bearer (?<token>[a-zA-Z0-9-._~+/]+=*)$",
5956
Pattern.CASE_INSENSITIVE);
6057

61-
private boolean allowUriQueryParameter = false;
62-
6358
private boolean allowFormEncodedBodyParameter = false;
6459

60+
private boolean allowUriQueryParameter = false;
61+
6562
private String bearerTokenHeaderName = HttpHeaders.AUTHORIZATION;
6663

6764
@Override
6865
public Mono<Authentication> convert(ServerWebExchange exchange) {
69-
return Mono.defer(() -> token(exchange)).map(token -> {
70-
if (token.isEmpty()) {
71-
BearerTokenError error = invalidTokenError();
72-
throw new OAuth2AuthenticationException(error);
73-
}
74-
return new BearerTokenAuthenticationToken(token);
66+
return Mono.defer(() -> {
67+
ServerHttpRequest request = exchange.getRequest();
68+
// @formatter:off
69+
return Flux.merge(resolveFromAuthorizationHeader(request.getHeaders()),
70+
resolveAccessTokenFromQueryString(request),
71+
resolveAccessTokenFromBody(exchange))
72+
.collectList()
73+
.flatMap(ServerBearerTokenAuthenticationConverter::resolveToken)
74+
.map(BearerTokenAuthenticationToken::new);
75+
// @formatter:on
7576
});
7677
}
7778

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-
});
79+
private static Mono<String> resolveToken(List<String> accessTokens) {
80+
if (CollectionUtils.isEmpty(accessTokens)) {
81+
return Mono.empty();
82+
}
83+
84+
if (accessTokens.size() > 1) {
85+
BearerTokenError error = BearerTokenErrors.invalidRequest("Found multiple bearer tokens in the request");
86+
return Mono.error(new OAuth2AuthenticationException(error));
87+
}
88+
89+
String accessToken = accessTokens.get(0);
90+
if (!StringUtils.hasText(accessToken)) {
91+
BearerTokenError error = BearerTokenErrors
92+
.invalidRequest("The requested token parameter is an empty string");
93+
return Mono.error(new OAuth2AuthenticationException(error));
94+
}
95+
96+
return Mono.just(accessToken);
9697
}
9798

98-
private static Mono<String> resolveAccessTokenFromRequest(ServerHttpRequest request) {
99-
List<String> parameterTokens = request.getQueryParams().get(ACCESS_TOKEN_NAME);
100-
if (CollectionUtils.isEmpty(parameterTokens)) {
99+
private Mono<String> resolveFromAuthorizationHeader(HttpHeaders headers) {
100+
String authorization = headers.getFirst(this.bearerTokenHeaderName);
101+
if (!StringUtils.startsWithIgnoreCase(authorization, "bearer")) {
101102
return Mono.empty();
102103
}
103-
if (parameterTokens.size() == 1) {
104-
return Mono.just(parameterTokens.get(0));
104+
105+
Matcher matcher = authorizationPattern.matcher(authorization);
106+
if (!matcher.matches()) {
107+
BearerTokenError error = BearerTokenErrors.invalidToken("Bearer token is malformed");
108+
throw new OAuth2AuthenticationException(error);
105109
}
106110

107-
BearerTokenError error = invalidRequest(MULTIPLE_BEARER_TOKENS_ERROR_MSG);
108-
throw new OAuth2AuthenticationException(error);
111+
return Mono.just(matcher.group("token"));
112+
}
113+
114+
private Flux<String> resolveAccessTokenFromQueryString(ServerHttpRequest request) {
115+
if (!this.allowUriQueryParameter || !HttpMethod.GET.equals(request.getMethod())) {
116+
return Flux.empty();
117+
}
109118

119+
return resolveTokens(request.getQueryParams());
110120
}
111121

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+
private Flux<String> resolveAccessTokenFromBody(ServerWebExchange exchange) {
123+
ServerHttpRequest request = exchange.getRequest();
124+
if (!this.allowFormEncodedBodyParameter
125+
|| !MediaType.APPLICATION_FORM_URLENCODED.equals(request.getHeaders().getContentType())
126+
|| !HttpMethod.POST.equals(request.getMethod())) {
127+
return Flux.empty();
122128
}
129+
130+
return exchange.getFormData().flatMapMany(ServerBearerTokenAuthenticationConverter::resolveTokens);
131+
}
132+
133+
private static Flux<String> resolveTokens(MultiValueMap<String, String> parameters) {
134+
List<String> accessTokens = parameters.get(ACCESS_TOKEN_PARAMETER_NAME);
135+
return CollectionUtils.isEmpty(accessTokens) ? Flux.empty() : Flux.fromIterable(accessTokens);
123136
}
124137

125138
/**
@@ -158,59 +171,4 @@ public void setAllowFormEncodedBodyParameter(boolean allowFormEncodedBodyParamet
158171
this.allowFormEncodedBodyParameter = allowFormEncodedBodyParameter;
159172
}
160173

161-
private Mono<String> resolveFromAuthorizationHeader(HttpHeaders headers) {
162-
String authorization = headers.getFirst(this.bearerTokenHeaderName);
163-
if (!StringUtils.startsWithIgnoreCase(authorization, "bearer")) {
164-
return Mono.empty();
165-
}
166-
Matcher matcher = authorizationPattern.matcher(authorization);
167-
if (!matcher.matches()) {
168-
BearerTokenError error = invalidTokenError();
169-
throw new OAuth2AuthenticationException(error);
170-
}
171-
return Mono.just(matcher.group("token"));
172-
}
173-
174-
private static BearerTokenError invalidTokenError() {
175-
return BearerTokenErrors.invalidToken("Bearer token is malformed");
176-
}
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-
210-
private boolean isParameterTokenSupportedForRequest(ServerHttpRequest request) {
211-
return this.allowUriQueryParameter && HttpMethod.GET.equals(request.getMethod());
212-
}
213-
214-
private enum TokenSource {HEADER, QUERY_PARAMETER, BODY_PARAMETER}
215-
216174
}

0 commit comments

Comments
 (0)