Add auto-config for reactive OAuth2 Resource Server
Closes gh-13948
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2012-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.boot.autoconfigure.security.oauth2.resource.reactive;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties;
|
||||
import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
|
||||
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
|
||||
|
||||
/**
|
||||
* {@link EnableAutoConfiguration Auto-configuration} for Reactive OAuth2 resource server
|
||||
* support.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @since 2.1.0
|
||||
*/
|
||||
@Configuration
|
||||
@AutoConfigureBefore(ReactiveSecurityAutoConfiguration.class)
|
||||
@EnableConfigurationProperties(OAuth2ResourceServerProperties.class)
|
||||
@ConditionalOnClass({ EnableWebFluxSecurity.class, BearerTokenAuthenticationToken.class })
|
||||
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
|
||||
@Import({ ReactiveOAuth2ResourceServerJwkConfiguration.class,
|
||||
ReactiveOAuth2ResourceServerWebSecurityConfiguration.class })
|
||||
public class ReactiveOAuth2ResourceServerAutoConfiguration {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2012-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.boot.autoconfigure.security.oauth2.resource.reactive;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
|
||||
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
|
||||
|
||||
/**
|
||||
* Configures a {@link ReactiveJwtDecoder} when a JWK Set URI is available.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
@Configuration
|
||||
class ReactiveOAuth2ResourceServerJwkConfiguration {
|
||||
|
||||
private final OAuth2ResourceServerProperties properties;
|
||||
|
||||
ReactiveOAuth2ResourceServerJwkConfiguration(
|
||||
OAuth2ResourceServerProperties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(name = "spring.security.oauth2.resource.jwt.jwk.set-uri")
|
||||
@ConditionalOnMissingBean
|
||||
public ReactiveJwtDecoder jwtDecoder() {
|
||||
return new NimbusReactiveJwtDecoder(
|
||||
this.properties.getJwt().getJwk().getSetUri());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2012-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.boot.autoconfigure.security.oauth2.resource.reactive;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
|
||||
/**
|
||||
* Configures a {@link SecurityWebFilterChain} for Reactive OAuth2 resource server support
|
||||
* if a {@link ReactiveJwtDecoder} bean is present.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnBean(ReactiveJwtDecoder.class)
|
||||
class ReactiveOAuth2ResourceServerWebSecurityConfiguration {
|
||||
|
||||
private final ReactiveJwtDecoder jwtDecoder;
|
||||
|
||||
ReactiveOAuth2ResourceServerWebSecurityConfiguration(ReactiveJwtDecoder jwtDecoder) {
|
||||
this.jwtDecoder = jwtDecoder;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
||||
http.authorizeExchange().anyExchange().authenticated().and().oauth2()
|
||||
.resourceServer().jwt().jwtDecoder(this.jwtDecoder);
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2012-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Auto-configuration for Spring Security's Reactive OAuth2 resource server.
|
||||
*/
|
||||
package org.springframework.boot.autoconfigure.security.oauth2.resource.reactive;
|
||||
@@ -106,6 +106,7 @@ org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.task.TaskExecutorAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Copyright 2012-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.boot.autoconfigure.security.oauth2.resource.reactive;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||
import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext;
|
||||
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.ReactiveAuthenticationManager;
|
||||
import org.springframework.security.config.BeanIds;
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
|
||||
import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
|
||||
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
|
||||
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtReactiveAuthenticationManager;
|
||||
import org.springframework.security.web.server.MatcherSecurityWebFilterChain;
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link ReactiveOAuth2ResourceServerAutoConfiguration}.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
public class ReactiveOAuth2ResourceServerAutoConfigurationTests {
|
||||
|
||||
private ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations
|
||||
.of(ReactiveOAuth2ResourceServerAutoConfiguration.class))
|
||||
.withUserConfiguration(TestConfig.class);
|
||||
|
||||
@Test
|
||||
public void autoConfigurationShouldConfigureResourceServer() {
|
||||
this.contextRunner.withPropertyValues(
|
||||
"spring.security.oauth2.resource.jwt.jwk.set-uri=http://jwk-set-uri.com")
|
||||
.run((context) -> {
|
||||
assertThat(context.getBean(ReactiveJwtDecoder.class))
|
||||
.isInstanceOf(NimbusReactiveJwtDecoder.class);
|
||||
assertFilterConfiguredWithJwtAuthenticationManager(context);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void autoConfigurationWhenJwkSetUriNullShouldNotFail() {
|
||||
this.contextRunner.run((context) -> assertThat(context)
|
||||
.doesNotHaveBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void jwtDecoderBeanIsConditionalOnMissingBean() {
|
||||
this.contextRunner.withPropertyValues(
|
||||
"spring.security.oauth2.resource.jwt.jwk.set-uri=http://jwk-set-uri.com")
|
||||
.withUserConfiguration(JwtDecoderConfig.class)
|
||||
.run((this::assertFilterConfiguredWithJwtAuthenticationManager));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void autoConfigurationShouldBeConditionalOnBearerTokenAuthenticationTokenClass() {
|
||||
this.contextRunner.withPropertyValues(
|
||||
"spring.security.oauth2.resource.jwt.jwk.set-uri=http://jwk-set-uri.com")
|
||||
.withUserConfiguration(JwtDecoderConfig.class)
|
||||
.withClassLoader(
|
||||
new FilteredClassLoader(BearerTokenAuthenticationToken.class))
|
||||
.run((context) -> assertThat(context)
|
||||
.doesNotHaveBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void autoConfigurationWhenSecurityWebFilterChainConfigPresentShouldNotAddOne() {
|
||||
this.contextRunner.withPropertyValues(
|
||||
"spring.security.oauth2.resource.jwt.jwk.set-uri=http://jwk-set-uri.com")
|
||||
.withUserConfiguration(SecurityWebFilterChainConfig.class)
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(SecurityWebFilterChain.class);
|
||||
assertThat(context).hasBean("testSpringSecurityFilterChain");
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void assertFilterConfiguredWithJwtAuthenticationManager(
|
||||
AssertableReactiveWebApplicationContext context) {
|
||||
MatcherSecurityWebFilterChain filterChain = (MatcherSecurityWebFilterChain) context
|
||||
.getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN);
|
||||
List<WebFilter> filters = (List<WebFilter>) ReflectionTestUtils
|
||||
.getField(filterChain, "filters");
|
||||
AuthenticationWebFilter webFilter = (AuthenticationWebFilter) filters.stream()
|
||||
.filter((f) -> f instanceof AuthenticationWebFilter).findFirst()
|
||||
.orElse(null);
|
||||
ReactiveAuthenticationManager authenticationManager = (ReactiveAuthenticationManager) ReflectionTestUtils
|
||||
.getField(webFilter, "authenticationManager");
|
||||
assertThat(authenticationManager)
|
||||
.isInstanceOf(JwtReactiveAuthenticationManager.class);
|
||||
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
static class TestConfig {
|
||||
|
||||
@Bean
|
||||
public MapReactiveUserDetailsService userDetailsService() {
|
||||
return mock(MapReactiveUserDetailsService.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class JwtDecoderConfig {
|
||||
|
||||
@Bean
|
||||
public ReactiveJwtDecoder decoder() {
|
||||
return mock(ReactiveJwtDecoder.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class SecurityWebFilterChainConfig {
|
||||
|
||||
@Bean
|
||||
SecurityWebFilterChain testSpringSecurityFilterChain(ServerHttpSecurity http,
|
||||
ReactiveJwtDecoder decoder) {
|
||||
http.authorizeExchange().pathMatchers("/message/**").hasRole("ADMIN")
|
||||
.anyExchange().authenticated().and().oauth2().resourceServer().jwt()
|
||||
.jwtDecoder(decoder);
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -165,7 +165,7 @@
|
||||
<spring-plugin.version>1.2.0.RELEASE</spring-plugin.version>
|
||||
<spring-restdocs.version>2.0.2.RELEASE</spring-restdocs.version>
|
||||
<spring-retry.version>1.2.2.RELEASE</spring-retry.version>
|
||||
<spring-security.version>5.1.0.M2</spring-security.version>
|
||||
<spring-security.version>5.1.0.BUILD-SNAPSHOT</spring-security.version>
|
||||
<spring-session-bom.version>Bean-M1</spring-session-bom.version>
|
||||
<spring-ws.version>3.0.3.RELEASE</spring-ws.version>
|
||||
<sqlite-jdbc.version>3.23.1</sqlite-jdbc.version>
|
||||
|
||||
@@ -3286,7 +3286,10 @@ following example:
|
||||
spring.security.oauth2.resource.jwt.jwk.set-uri=https://example.com/oauth2/default/v1/keys
|
||||
----
|
||||
|
||||
Alternatively, you can define your own `JwtDecoder` bean.
|
||||
The same properties are applicable for both servlet and reactive applications.
|
||||
|
||||
Alternatively, you can define your own `JwtDecoder` bean for servlet applications
|
||||
or a `ReactiveJwtDecoder` for reactive applications.
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user