diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java b/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java
index cffdad7824..fac8ba9b6e 100644
--- a/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java
+++ b/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java
@@ -2108,6 +2108,55 @@ public final class HttpSecurity extends
return configurer;
}
+ /**
+ * Configures OAuth 2.0 Resource Server support.
+ *
+ *
Example Configuration
+ *
+ * The following example demonstrates how to configure a custom JWT authentication converter.
+ *
+ *
+ * @Configuration
+ * @EnableWebSecurity
+ * public class OAuth2ClientSecurityConfig extends WebSecurityConfigurerAdapter {
+ * @Override
+ * protected void configure(HttpSecurity http) throws Exception {
+ * http
+ * .authorizeRequests(authorizeRequests ->
+ * authorizeRequests
+ * .anyRequest().authenticated()
+ * )
+ * .oauth2ResourceServer(oauth2ResourceServer ->
+ * oauth2ResourceServer
+ * .jwt(jwt ->
+ * jwt
+ * .jwtAuthenticationConverter(jwtDecoder())
+ * )
+ * );
+ * }
+ *
+ * @Bean
+ * public JwtDecoder jwtDecoder() {
+ * return JwtDecoders.fromOidcIssuerLocation(issuerUri);
+ * }
+ * }
+ *
+ *
+ * @see OAuth 2.0 Authorization Framework
+ *
+ * @param oauth2ResourceServerCustomizer the {@link Customizer} to provide more options for
+ * the {@link OAuth2ResourceServerConfigurer}
+ * @return the {@link HttpSecurity} for further customizations
+ * @throws Exception
+ */
+ public HttpSecurity oauth2ResourceServer(Customizer> oauth2ResourceServerCustomizer)
+ throws Exception {
+ OAuth2ResourceServerConfigurer configurer = getOrApply(new OAuth2ResourceServerConfigurer<>(getContext()));
+ this.postProcess(configurer);
+ oauth2ResourceServerCustomizer.customize(configurer);
+ return HttpSecurity.this;
+ }
+
/**
* Configures channel security. In order for this configuration to be useful at least
* one mapping to a required channel must be provided.
diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java
index 2d4ffb0cbd..589dd391e5 100644
--- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java
+++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java
@@ -25,6 +25,7 @@ import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationManagerResolver;
import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer;
@@ -65,11 +66,12 @@ import static org.springframework.security.oauth2.jwt.NimbusJwtDecoder.withJwkSe
* {@link #accessDeniedHandler(AccessDeniedHandler)} - customizes how access denied errors are handled
* {@link #authenticationEntryPoint(AuthenticationEntryPoint)} - customizes how authentication failures are handled
* {@link #bearerTokenResolver(BearerTokenResolver)} - customizes how to resolve a bearer token from the request
- * {@link #jwt()} - enables Jwt-encoded bearer token support
+ * {@link #jwt(Customizer)} - enables Jwt-encoded bearer token support
+ * {@link #opaqueToken(Customizer)} - enables opaque bearer token support
*
*
*
- * When using {@link #jwt()}, either
+ * When using {@link #jwt(Customizer)}, either
*
*
* -
@@ -83,7 +85,7 @@ import static org.springframework.security.oauth2.jwt.NimbusJwtDecoder.withJwkSe
*
*
*
- * Also with {@link #jwt()} consider
+ * Also with {@link #jwt(Customizer)} consider
*
*
* -
@@ -93,12 +95,12 @@ import static org.springframework.security.oauth2.jwt.NimbusJwtDecoder.withJwkSe
*
*
*
- * When using {@link #opaque()}, supply an introspection endpoint and its authentication configuration
+ * When using {@link #opaqueToken(Customizer)}, supply an introspection endpoint and its authentication configuration
*
*
* Security Filters
*
- * The following {@code Filter}s are populated when {@link #jwt()} is configured:
+ * The following {@code Filter}s are populated when {@link #jwt(Customizer)} is configured:
*
*
* - {@link BearerTokenAuthenticationFilter}
@@ -180,6 +182,22 @@ public final class OAuth2ResourceServerConfigurer jwt(Customizer jwtCustomizer) throws Exception {
+ if ( this.jwtConfigurer == null ) {
+ this.jwtConfigurer = new JwtConfigurer(this.context);
+ }
+ jwtCustomizer.customize(this.jwtConfigurer);
+ return this;
+ }
+
public OpaqueTokenConfigurer opaqueToken() {
if (this.opaqueTokenConfigurer == null) {
this.opaqueTokenConfigurer = new OpaqueTokenConfigurer(this.context);
@@ -188,6 +206,23 @@ public final class OAuth2ResourceServerConfigurer opaqueToken(Customizer opaqueTokenCustomizer)
+ throws Exception {
+ if (this.opaqueTokenConfigurer == null) {
+ this.opaqueTokenConfigurer = new OpaqueTokenConfigurer(this.context);
+ }
+ opaqueTokenCustomizer.customize(this.opaqueTokenConfigurer);
+ return this;
+ }
+
@Override
public void init(H http) throws Exception {
registerDefaultAccessDeniedHandler(http);
diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java
index 995e7d98a8..494ff7d74b 100644
--- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java
+++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java
@@ -127,6 +127,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static org.springframework.security.config.Customizer.withDefaults;
import static org.springframework.security.oauth2.core.TestOAuth2AccessTokens.noScopes;
import static org.springframework.security.oauth2.jwt.NimbusJwtDecoder.withJwkSetUri;
import static org.springframework.security.oauth2.jwt.NimbusJwtDecoder.withPublicKey;
@@ -184,6 +185,19 @@ public class OAuth2ResourceServerConfigurerTests {
.andExpect(content().string("ok"));
}
+ @Test
+ public void getWhenUsingDefaultsInLambdaWithValidBearerTokenThenAcceptsRequest()
+ throws Exception {
+
+ this.spring.register(RestOperationsConfig.class, DefaultInLambdaConfig.class, BasicController.class).autowire();
+ mockRestOperations(jwks("Default"));
+ String token = this.token("ValidNoScopes");
+
+ this.mvc.perform(get("/").with(bearerToken(token)))
+ .andExpect(status().isOk())
+ .andExpect(content().string("ok"));
+ }
+
@Test
public void getWhenUsingJwkSetUriThenAcceptsRequest() throws Exception {
this.spring.register(WebServerConfig.class, JwkSetUriConfig.class, BasicController.class).autowire();
@@ -195,6 +209,16 @@ public class OAuth2ResourceServerConfigurerTests {
.andExpect(content().string("ok"));
}
+ @Test
+ public void getWhenUsingJwkSetUriInLambdaThenAcceptsRequest() throws Exception {
+ this.spring.register(WebServerConfig.class, JwkSetUriInLambdaConfig.class, BasicController.class).autowire();
+ mockWebServer(jwks("Default"));
+ String token = this.token("ValidNoScopes");
+
+ this.mvc.perform(get("/").with(bearerToken(token)))
+ .andExpect(status().isOk())
+ .andExpect(content().string("ok"));
+ }
@Test
public void getWhenUsingDefaultsWithExpiredBearerTokenThenInvalidToken()
@@ -756,6 +780,23 @@ public class OAuth2ResourceServerConfigurerTests {
.andExpect(content().string(JWT_SUBJECT));
}
+ @Test
+ public void requestWhenCustomJwtDecoderInLambdaOnDslThenUsed()
+ throws Exception {
+
+ this.spring.register(CustomJwtDecoderInLambdaOnDsl.class, BasicController.class).autowire();
+
+ CustomJwtDecoderInLambdaOnDsl config = this.spring.getContext().getBean(CustomJwtDecoderInLambdaOnDsl.class);
+ JwtDecoder decoder = config.decoder();
+
+ when(decoder.decode(anyString())).thenReturn(JWT);
+
+ this.mvc.perform(get("/authenticated")
+ .with(bearerToken(JWT_TOKEN)))
+ .andExpect(status().isOk())
+ .andExpect(content().string(JWT_SUBJECT));
+ }
+
@Test
public void requestWhenCustomJwtDecoderExposedAsBeanThenUsed()
throws Exception {
@@ -1067,6 +1108,17 @@ public class OAuth2ResourceServerConfigurerTests {
.andExpect(content().string("test-subject"));
}
+ @Test
+ public void getWhenOpaqueTokenInLambdaAndIntrospectingThenOk() throws Exception {
+ this.spring.register(RestOperationsConfig.class, OpaqueTokenInLambdaConfig.class, BasicController.class).autowire();
+ mockRestOperations(json("Active"));
+
+ this.mvc.perform(get("/authenticated")
+ .with(bearerToken("token")))
+ .andExpect(status().isOk())
+ .andExpect(content().string("test-subject"));
+ }
+
@Test
public void getWhenIntrospectionFailsThenUnauthorized() throws Exception {
this.spring.register(RestOperationsConfig.class, OpaqueTokenConfig.class).autowire();
@@ -1104,6 +1156,20 @@ public class OAuth2ResourceServerConfigurerTests {
verifyBean(AuthenticationProvider.class).authenticate(any(Authentication.class));
}
+ @Test
+ public void getWhenCustomIntrospectionAuthenticationManagerInLambdaThenUsed() throws Exception {
+ this.spring.register(OpaqueTokenAuthenticationManagerInLambdaConfig.class, BasicController.class).autowire();
+
+ when(bean(AuthenticationProvider.class).authenticate(any(Authentication.class)))
+ .thenReturn(INTROSPECTION_AUTHENTICATION_TOKEN);
+ this.mvc.perform(get("/authenticated")
+ .with(bearerToken("token")))
+ .andExpect(status().isOk())
+ .andExpect(content().string("mock-test-subject"));
+
+ verifyBean(AuthenticationProvider.class).authenticate(any(Authentication.class));
+ }
+
@Test
public void configureWhenOnlyIntrospectionUrlThenException() throws Exception {
assertThatCode(() -> this.spring.register(OpaqueTokenHalfConfiguredConfig.class).autowire())
@@ -1311,6 +1377,26 @@ public class OAuth2ResourceServerConfigurerTests {
}
}
+ @EnableWebSecurity
+ static class DefaultInLambdaConfig extends WebSecurityConfigurerAdapter {
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ // @formatter:off
+ http
+ .authorizeRequests(authorizeRequests ->
+ authorizeRequests
+ .antMatchers("/requires-read-scope").access("hasAuthority('SCOPE_message:read')")
+ .anyRequest().authenticated()
+ )
+ .oauth2ResourceServer(oauth2ResourceServer ->
+ oauth2ResourceServer
+ .jwt(withDefaults())
+ );
+ // @formatter:on
+ }
+ }
+
@EnableWebSecurity
static class JwkSetUriConfig extends WebSecurityConfigurerAdapter {
@Value("${mockwebserver.url:https://example.org}")
@@ -1331,6 +1417,31 @@ public class OAuth2ResourceServerConfigurerTests {
}
}
+ @EnableWebSecurity
+ static class JwkSetUriInLambdaConfig extends WebSecurityConfigurerAdapter {
+ @Value("${mockwebserver.url:https://example.org}")
+ String jwkSetUri;
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ // @formatter:off
+ http
+ .authorizeRequests(authorizeRequests ->
+ authorizeRequests
+ .antMatchers("/requires-read-scope").access("hasAuthority('SCOPE_message:read')")
+ .anyRequest().authenticated()
+ )
+ .oauth2ResourceServer(oauth2ResourceServer ->
+ oauth2ResourceServer
+ .jwt(jwt ->
+ jwt
+ .jwkSetUri(this.jwkSetUri)
+ )
+ );
+ // @formatter:on
+ }
+ }
+
@EnableWebSecurity
static class CsrfDisabledConfig extends WebSecurityConfigurerAdapter {
@Value("${mockwebserver.url:https://example.org}")
@@ -1677,6 +1788,33 @@ public class OAuth2ResourceServerConfigurerTests {
}
}
+ @EnableWebSecurity
+ static class CustomJwtDecoderInLambdaOnDsl extends WebSecurityConfigurerAdapter {
+ JwtDecoder decoder = mock(JwtDecoder.class);
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ // @formatter:off
+ http
+ .authorizeRequests(authorizeRequests ->
+ authorizeRequests
+ .anyRequest().authenticated()
+ )
+ .oauth2ResourceServer(oauth2ResourceServer ->
+ oauth2ResourceServer
+ .jwt(jwt ->
+ jwt
+ .decoder(decoder())
+ )
+ );
+ // @formatter:on
+ }
+
+ JwtDecoder decoder() {
+ return this.decoder;
+ }
+ }
+
@EnableWebSecurity
static class CustomJwtDecoderAsBean extends WebSecurityConfigurerAdapter {
@Override
@@ -1831,6 +1969,25 @@ public class OAuth2ResourceServerConfigurerTests {
}
}
+ @EnableWebSecurity
+ static class OpaqueTokenInLambdaConfig extends WebSecurityConfigurerAdapter {
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ // @formatter:off
+ http
+ .authorizeRequests(authorizeRequests ->
+ authorizeRequests
+ .antMatchers("/requires-read-scope").hasAuthority("SCOPE_message:read")
+ .anyRequest().authenticated()
+ )
+ .oauth2ResourceServer(oauth2ResourceServer ->
+ oauth2ResourceServer
+ .opaqueToken(withDefaults())
+ );
+ // @formatter:on
+ }
+ }
+
@EnableWebSecurity
static class OpaqueTokenAuthenticationManagerConfig extends WebSecurityConfigurerAdapter {
@Override
@@ -1852,6 +2009,32 @@ public class OAuth2ResourceServerConfigurerTests {
}
}
+ @EnableWebSecurity
+ static class OpaqueTokenAuthenticationManagerInLambdaConfig extends WebSecurityConfigurerAdapter {
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ // @formatter:off
+ http
+ .authorizeRequests(authorizeRequests ->
+ authorizeRequests
+ .anyRequest().authenticated()
+ )
+ .oauth2ResourceServer(oauth2ResourceServer ->
+ oauth2ResourceServer
+ .opaqueToken(opaqueToken ->
+ opaqueToken
+ .authenticationManager(authenticationProvider()::authenticate)
+ )
+ );
+ // @formatter:on
+ }
+
+ @Bean
+ public AuthenticationProvider authenticationProvider() {
+ return mock(AuthenticationProvider.class);
+ }
+ }
+
@EnableWebSecurity
static class OpaqueAndJwtConfig extends WebSecurityConfigurerAdapter {
@Override