|
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