|
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