|
23 | 23 | import java.util.function.Supplier;
|
24 | 24 |
|
25 | 25 | import jakarta.servlet.http.HttpServletRequest;
|
| 26 | +import org.apache.commons.logging.Log; |
| 27 | +import org.apache.commons.logging.LogFactory; |
26 | 28 |
|
27 | 29 | import org.springframework.context.ApplicationContext;
|
28 | 30 | import org.springframework.core.convert.converter.Converter;
|
|
37 | 39 | import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer;
|
38 | 40 | import org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer;
|
39 | 41 | import org.springframework.security.config.http.SessionCreationPolicy;
|
| 42 | +import org.springframework.security.core.Authentication; |
40 | 43 | import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
41 | 44 | import org.springframework.security.oauth2.jwt.Jwt;
|
42 | 45 | import org.springframework.security.oauth2.jwt.JwtDecoder;
|
43 | 46 | import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
| 47 | +import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken; |
44 | 48 | import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
|
45 | 49 | import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
|
46 | 50 | import org.springframework.security.oauth2.server.resource.authentication.OpaqueTokenAuthenticationProvider;
|
|
49 | 53 | import org.springframework.security.oauth2.server.resource.introspection.SpringOpaqueTokenIntrospector;
|
50 | 54 | import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
|
51 | 55 | import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
|
52 |
| -import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver; |
53 | 56 | import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler;
|
| 57 | +import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationConverter; |
54 | 58 | import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter;
|
55 | 59 | import org.springframework.security.web.AuthenticationEntryPoint;
|
56 | 60 | import org.springframework.security.web.access.AccessDeniedHandler;
|
57 | 61 | import org.springframework.security.web.access.AccessDeniedHandlerImpl;
|
58 | 62 | import org.springframework.security.web.access.DelegatingAccessDeniedHandler;
|
| 63 | +import org.springframework.security.web.authentication.AuthenticationConverter; |
59 | 64 | import org.springframework.security.web.csrf.CsrfException;
|
60 | 65 | import org.springframework.security.web.util.matcher.AndRequestMatcher;
|
61 | 66 | import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
|
|
64 | 69 | import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;
|
65 | 70 | import org.springframework.security.web.util.matcher.RequestMatcher;
|
66 | 71 | import org.springframework.util.Assert;
|
| 72 | +import org.springframework.util.StringUtils; |
67 | 73 | import org.springframework.web.accept.ContentNegotiationStrategy;
|
68 | 74 | import org.springframework.web.accept.HeaderContentNegotiationStrategy;
|
69 | 75 |
|
70 | 76 | /**
|
71 |
| - * |
72 | 77 | * An {@link AbstractHttpConfigurer} for OAuth 2.0 Resource Server Support.
|
73 |
| - * |
| 78 | + * <p> |
74 | 79 | * By default, this wires a {@link BearerTokenAuthenticationFilter}, which can be used to
|
75 | 80 | * parse the request for bearer tokens and make an authentication attempt.
|
76 | 81 | *
|
|
84 | 89 | * authentication failures are handled
|
85 | 90 | * <li>{@link #bearerTokenResolver(BearerTokenResolver)} - customizes how to resolve a
|
86 | 91 | * bearer token from the request</li>
|
| 92 | + * <li>{@link #authenticationConverter(AuthenticationConverter)} - customizes how to |
| 93 | + * convert a request to authentication</li> |
87 | 94 | * <li>{@link #jwt(Customizer)} - enables Jwt-encoded bearer token support</li>
|
88 | 95 | * <li>{@link #opaqueToken(Customizer)} - enables opaque bearer token support</li>
|
89 | 96 | * </ul>
|
|
96 | 103 | * <li>supply a {@link JwtDecoder} instance via {@link JwtConfigurer#decoder}, or</li>
|
97 | 104 | * <li>expose a {@link JwtDecoder} bean</li>
|
98 | 105 | * </ul>
|
99 |
| - * |
| 106 | + * <p> |
100 | 107 | * Also with {@link #jwt(Customizer)} consider
|
101 | 108 | *
|
102 | 109 | * <ul>
|
|
111 | 118 | * </p>
|
112 | 119 | *
|
113 | 120 | * <h2>Security Filters</h2>
|
114 |
| - * |
| 121 | + * <p> |
115 | 122 | * The following {@code Filter}s are populated when {@link #jwt(Customizer)} is
|
116 | 123 | * configured:
|
117 | 124 | *
|
|
120 | 127 | * </ul>
|
121 | 128 | *
|
122 | 129 | * <h2>Shared Objects Created</h2>
|
123 |
| - * |
| 130 | + * <p> |
124 | 131 | * The following shared objects are populated:
|
125 | 132 | *
|
126 | 133 | * <ul>
|
127 | 134 | * <li>{@link SessionCreationPolicy} (optional)</li>
|
128 | 135 | * </ul>
|
129 | 136 | *
|
130 | 137 | * <h2>Shared Objects Used</h2>
|
131 |
| - * |
| 138 | + * <p> |
132 | 139 | * The following shared objects are used:
|
133 | 140 | *
|
134 | 141 | * <ul>
|
@@ -156,7 +163,7 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
|
156 | 163 |
|
157 | 164 | private AuthenticationManagerResolver<HttpServletRequest> authenticationManagerResolver;
|
158 | 165 |
|
159 |
| - private BearerTokenResolver bearerTokenResolver; |
| 166 | + private AuthenticationConverter authenticationConverter; |
160 | 167 |
|
161 | 168 | private JwtConfigurer jwtConfigurer;
|
162 | 169 |
|
@@ -194,9 +201,16 @@ public OAuth2ResourceServerConfigurer<H> authenticationManagerResolver(
|
194 | 201 | return this;
|
195 | 202 | }
|
196 | 203 |
|
| 204 | + @Deprecated |
197 | 205 | public OAuth2ResourceServerConfigurer<H> bearerTokenResolver(BearerTokenResolver bearerTokenResolver) {
|
198 | 206 | Assert.notNull(bearerTokenResolver, "bearerTokenResolver cannot be null");
|
199 |
| - this.bearerTokenResolver = bearerTokenResolver; |
| 207 | + this.authenticationConverter = new BearerTokenResolverAuthenticationConverterAdapter(bearerTokenResolver); |
| 208 | + return this; |
| 209 | + } |
| 210 | + |
| 211 | + public OAuth2ResourceServerConfigurer<H> authenticationConverter(AuthenticationConverter authenticationConverter) { |
| 212 | + Assert.notNull(authenticationConverter, "authenticationConverter cannot be null"); |
| 213 | + this.authenticationConverter = authenticationConverter; |
200 | 214 | return this;
|
201 | 215 | }
|
202 | 216 |
|
@@ -271,16 +285,16 @@ public void init(H http) {
|
271 | 285 |
|
272 | 286 | @Override
|
273 | 287 | public void configure(H http) {
|
274 |
| - BearerTokenResolver bearerTokenResolver = getBearerTokenResolver(); |
275 |
| - this.requestMatcher.setBearerTokenResolver(bearerTokenResolver); |
276 | 288 | AuthenticationManagerResolver resolver = this.authenticationManagerResolver;
|
277 | 289 | if (resolver == null) {
|
278 | 290 | AuthenticationManager authenticationManager = getAuthenticationManager(http);
|
279 | 291 | resolver = (request) -> authenticationManager;
|
280 | 292 | }
|
281 | 293 |
|
282 | 294 | BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(resolver);
|
283 |
| - filter.setBearerTokenResolver(bearerTokenResolver); |
| 295 | + AuthenticationConverter converter = getAuthenticationConverter(); |
| 296 | + this.requestMatcher.setAuthenticationConverter(converter); |
| 297 | + filter.setAuthenticationConverter(converter); |
284 | 298 | filter.setAuthenticationEntryPoint(this.authenticationEntryPoint);
|
285 | 299 | filter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
|
286 | 300 | filter = postProcess(filter);
|
@@ -363,16 +377,29 @@ AuthenticationManager getAuthenticationManager(H http) {
|
363 | 377 | return http.getSharedObject(AuthenticationManager.class);
|
364 | 378 | }
|
365 | 379 |
|
| 380 | + AuthenticationConverter getAuthenticationConverter() { |
| 381 | + if (this.authenticationConverter != null) { |
| 382 | + return this.authenticationConverter; |
| 383 | + } |
| 384 | + if (this.context.getBeanNamesForType(BearerTokenAuthenticationConverter.class).length > 0) { |
| 385 | + this.authenticationConverter = this.context.getBean(BearerTokenAuthenticationConverter.class); |
| 386 | + } |
| 387 | + else if (this.context.getBeanNamesForType(BearerTokenResolver.class).length > 0) { |
| 388 | + BearerTokenResolver bearerTokenResolver = this.context.getBean(BearerTokenResolver.class); |
| 389 | + this.authenticationConverter = new BearerTokenResolverAuthenticationConverterAdapter(bearerTokenResolver); |
| 390 | + } |
| 391 | + else { |
| 392 | + this.authenticationConverter = new BearerTokenAuthenticationConverter(); |
| 393 | + } |
| 394 | + return this.authenticationConverter; |
| 395 | + } |
| 396 | + |
366 | 397 | BearerTokenResolver getBearerTokenResolver() {
|
367 |
| - if (this.bearerTokenResolver == null) { |
368 |
| - if (this.context.getBeanNamesForType(BearerTokenResolver.class).length > 0) { |
369 |
| - this.bearerTokenResolver = this.context.getBean(BearerTokenResolver.class); |
370 |
| - } |
371 |
| - else { |
372 |
| - this.bearerTokenResolver = new DefaultBearerTokenResolver(); |
373 |
| - } |
| 398 | + AuthenticationConverter authenticationConverter = getAuthenticationConverter(); |
| 399 | + if (authenticationConverter instanceof BearerTokenResolverAuthenticationConverterAdapter bearer) { |
| 400 | + return bearer.bearerTokenResolver; |
374 | 401 | }
|
375 |
| - return this.bearerTokenResolver; |
| 402 | + return null; |
376 | 403 | }
|
377 | 404 |
|
378 | 405 | public class JwtConfigurer {
|
@@ -560,21 +587,43 @@ AuthenticationManager getAuthenticationManager(H http) {
|
560 | 587 |
|
561 | 588 | private static final class BearerTokenRequestMatcher implements RequestMatcher {
|
562 | 589 |
|
563 |
| - private BearerTokenResolver bearerTokenResolver; |
| 590 | + private AuthenticationConverter authenticationConverter; |
564 | 591 |
|
565 | 592 | @Override
|
566 | 593 | public boolean matches(HttpServletRequest request) {
|
567 | 594 | try {
|
568 |
| - return this.bearerTokenResolver.resolve(request) != null; |
| 595 | + return this.authenticationConverter.convert(request) != null; |
569 | 596 | }
|
570 | 597 | catch (OAuth2AuthenticationException ex) {
|
571 | 598 | return false;
|
572 | 599 | }
|
573 | 600 | }
|
574 | 601 |
|
575 |
| - void setBearerTokenResolver(BearerTokenResolver tokenResolver) { |
576 |
| - Assert.notNull(tokenResolver, "resolver cannot be null"); |
577 |
| - this.bearerTokenResolver = tokenResolver; |
| 602 | + void setAuthenticationConverter(AuthenticationConverter authenticationConverter) { |
| 603 | + Assert.notNull(authenticationConverter, "authenticationConverter cannot be null"); |
| 604 | + this.authenticationConverter = authenticationConverter; |
| 605 | + } |
| 606 | + |
| 607 | + } |
| 608 | + |
| 609 | + private static final class BearerTokenResolverAuthenticationConverterAdapter implements AuthenticationConverter { |
| 610 | + |
| 611 | + private final Log logger = LogFactory.getLog(BearerTokenResolverAuthenticationConverterAdapter.class); |
| 612 | + |
| 613 | + private final BearerTokenResolver bearerTokenResolver; |
| 614 | + |
| 615 | + BearerTokenResolverAuthenticationConverterAdapter(BearerTokenResolver bearerTokenResolver) { |
| 616 | + this.bearerTokenResolver = bearerTokenResolver; |
| 617 | + } |
| 618 | + |
| 619 | + @Override |
| 620 | + public Authentication convert(HttpServletRequest request) { |
| 621 | + String token = this.bearerTokenResolver.resolve(request); |
| 622 | + if (!StringUtils.hasText(token)) { |
| 623 | + this.logger.trace("Did not process request since did not find bearer token"); |
| 624 | + return null; |
| 625 | + } |
| 626 | + return new BearerTokenAuthenticationToken(token); |
578 | 627 | }
|
579 | 628 |
|
580 | 629 | }
|
|
0 commit comments