Add auto-config for reactive OAuth2 Resource Server

Closes gh-13948
This commit is contained in:
Madhura Bhave
2018-08-07 15:09:33 -07:00
parent 319fec4be4
commit bc6e4e6e55
14 changed files with 558 additions and 2 deletions

View File

@@ -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 {
}

View File

@@ -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());
}
}

View File

@@ -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();
}
}

View File

@@ -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;

View File

@@ -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,\

View File

@@ -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();
}
}
}

View File

@@ -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>

View File

@@ -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.