|
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 |
| - |
21 | 19 | import java.util.List;
|
22 | 20 | import java.util.regex.Matcher;
|
23 | 21 | import java.util.regex.Pattern;
|
24 | 22 |
|
25 | 23 | import reactor.core.publisher.Flux;
|
26 | 24 | import reactor.core.publisher.Mono;
|
27 |
| -import reactor.util.function.Tuple2; |
28 |
| -import reactor.util.function.Tuples; |
29 | 25 |
|
30 | 26 | import org.springframework.http.HttpHeaders;
|
31 | 27 | import org.springframework.http.HttpMethod;
|
|
38 | 34 | import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken;
|
39 | 35 | import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
|
40 | 36 | import org.springframework.util.CollectionUtils;
|
| 37 | +import org.springframework.util.MultiValueMap; |
41 | 38 | import org.springframework.util.StringUtils;
|
42 | 39 | import org.springframework.web.server.ServerWebExchange;
|
43 | 40 |
|
|
53 | 50 | */
|
54 | 51 | public class ServerBearerTokenAuthenticationConverter implements ServerAuthenticationConverter {
|
55 | 52 |
|
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 | + |
58 | 55 | private static final Pattern authorizationPattern = Pattern.compile("^Bearer (?<token>[a-zA-Z0-9-._~+/]+=*)$",
|
59 | 56 | Pattern.CASE_INSENSITIVE);
|
60 | 57 |
|
61 |
| - private boolean allowUriQueryParameter = false; |
62 |
| - |
63 | 58 | private boolean allowFormEncodedBodyParameter = false;
|
64 | 59 |
|
| 60 | + private boolean allowUriQueryParameter = false; |
| 61 | + |
65 | 62 | private String bearerTokenHeaderName = HttpHeaders.AUTHORIZATION;
|
66 | 63 |
|
67 | 64 | @Override
|
68 | 65 | 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 |
75 | 76 | });
|
76 | 77 | }
|
77 | 78 |
|
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); |
96 | 97 | }
|
97 | 98 |
|
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")) { |
101 | 102 | return Mono.empty();
|
102 | 103 | }
|
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); |
105 | 109 | }
|
106 | 110 |
|
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 | + } |
109 | 118 |
|
| 119 | + return resolveTokens(request.getQueryParams()); |
110 | 120 | }
|
111 | 121 |
|
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(); |
122 | 128 | }
|
| 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); |
123 | 136 | }
|
124 | 137 |
|
125 | 138 | /**
|
@@ -158,59 +171,4 @@ public void setAllowFormEncodedBodyParameter(boolean allowFormEncodedBodyParamet
|
158 | 171 | this.allowFormEncodedBodyParameter = allowFormEncodedBodyParameter;
|
159 | 172 | }
|
160 | 173 |
|
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 |
| - |
216 | 174 | }
|
0 commit comments