1
0
mirror of synced 2026-08-05 01:36:56 +00:00

Add Multiple Reactive HttpSecurity

Fixes gh-4395
This commit is contained in:
Rob Winch
2017-06-14 14:05:52 -05:00
parent 406e1e6951
commit 9141a8a7c0
10 changed files with 281 additions and 15 deletions
@@ -18,8 +18,13 @@
package org.springframework.security.config.annotation.web.reactive;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.WebFilterChainFilter;
import java.util.List;
/**
* @author Rob Winch
@@ -28,4 +33,11 @@ import org.springframework.context.annotation.Configuration;
@Configuration
public class WebFluxSecurityConfiguration {
@Autowired(required = false)
private List<SecurityWebFilterChain> securityWebFilterChains;
@Bean
public WebFilterChainFilter springSecurityFilterChain() {
return WebFilterChainFilter.fromSecurityWebFilterChainsList(securityWebFilterChains);
}
}
@@ -20,10 +20,13 @@ import java.util.List;
import java.util.Optional;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.web.server.MatcherSecurityWebFilterChain;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.context.SecurityContextRepositoryWebFilter;
import org.springframework.security.web.server.WebFilterChainFilter;
import org.springframework.security.web.server.authorization.ExceptionTranslationWebFilter;
import org.springframework.security.web.server.context.SecurityContextRepository;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
import org.springframework.util.Assert;
import org.springframework.web.server.WebFilter;
@@ -32,6 +35,8 @@ import org.springframework.web.server.WebFilter;
* @since 5.0
*/
public class HttpSecurity {
private ServerWebExchangeMatcher securityMatcher = ServerWebExchangeMatchers.anyExchange();
private AuthorizeExchangeBuilder authorizeExchangeBuilder;
private HeaderBuilder headers = new HeaderBuilder();
@@ -40,6 +45,26 @@ public class HttpSecurity {
private Optional<SecurityContextRepository> securityContextRepository = Optional.empty();
/**
* The ServerExchangeMatcher that determines which requests apply to this HttpSecurity instance.
*
* @param matcher the ServerExchangeMatcher that determines which requests apply to this HttpSecurity instance.
* Default is all requests.
*/
public HttpSecurity securityMatcher(ServerWebExchangeMatcher matcher) {
Assert.notNull(matcher, "matcher cannot be null");
this.securityMatcher = matcher;
return this;
}
/**
* Gets the ServerExchangeMatcher that determines which requests apply to this HttpSecurity instance.
* @return the ServerExchangeMatcher that determines which requests apply to this HttpSecurity instance.
*/
private ServerWebExchangeMatcher getSecurityMatcher() {
return this.securityMatcher;
}
public HttpSecurity securityContextRepository(SecurityContextRepository securityContextRepository) {
Assert.notNull(securityContextRepository, "securityContextRepository cannot be null");
this.securityContextRepository = Optional.of(securityContextRepository);
@@ -69,7 +94,7 @@ public class HttpSecurity {
return this;
}
public WebFilterChainFilter build() {
public SecurityWebFilterChain build() {
List<WebFilter> filters = new ArrayList<>();
if(headers != null) {
filters.add(headers.build());
@@ -84,7 +109,7 @@ public class HttpSecurity {
filters.add(new ExceptionTranslationWebFilter());
filters.add(authorizeExchangeBuilder.build());
}
return new WebFilterChainFilter(filters);
return new MatcherSecurityWebFilterChain(getSecurityMatcher(), filters);
}
public static HttpSecurity http() {
@@ -346,7 +346,7 @@ public class NamespaceHttpTests extends BaseSpringSpec {
}
}
// http@request-matcher is not available (instead request matcher instances are used)
// http@request-matcher is not available (instead request securityMatcher instances are used)
def "http@request-matcher-ref ant"() {
when:
@@ -0,0 +1,98 @@
/*
*
* * Copyright 2002-2017 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.security.config.annotation.web.reactive;
import org.junit.Test;
import org.junit.experimental.runners.Enclosed;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.web.server.HttpSecurity;
import org.springframework.security.core.userdetails.MapUserDetailsRepository;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsRepository;
import org.springframework.security.test.web.reactive.server.WebTestClientBuilder;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.WebFilterChainFilter;
import org.springframework.security.web.server.util.matcher.PathMatcherServerWebExchangeMatcher;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.WebTestClient;
import static org.mockito.Mockito.mock;
/**
* @author Rob Winch
* @since 5.0
*/
@RunWith(Enclosed.class)
public class EnableWebFluxSecurityTests {
@RunWith(SpringRunner.class)
public static class MultiHttpSecurity {
@Autowired
WebFilterChainFilter springSecurityFilterChain;
@Test
public void multiWorks() {
WebTestClient client = WebTestClientBuilder.bindToWebFilters(springSecurityFilterChain).build();
client.get()
.uri("/api/test")
.exchange()
.expectStatus().isUnauthorized()
.expectBody().isEmpty();
client.get()
.uri("/test")
.exchange()
.expectStatus().isOk();
}
@EnableWebFluxSecurity
static class Config {
@Order(Ordered.HIGHEST_PRECEDENCE)
@Bean
public SecurityWebFilterChain apiHttpSecurity(HttpSecurity http) {
http
.securityMatcher(new PathMatcherServerWebExchangeMatcher("/api/**"))
.authorizeExchange()
.anyExchange().denyAll();
return http.build();
}
@Bean
public SecurityWebFilterChain httpSecurity(HttpSecurity http) {
return http.build();
}
@Bean
public UserDetailsRepository userDetailsRepository() {
return new MapUserDetailsRepository(User.withUsername("user")
.password("password")
.roles("USER")
.build()
);
}
}
}
}
@@ -27,12 +27,12 @@ import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.test.web.reactive.server.WebTestClientBuilder;
import org.springframework.security.web.server.WebFilterChainFilter;
import org.springframework.security.web.server.context.SecurityContextRepository;
import org.springframework.security.web.server.context.WebSessionSecurityContextRepository;
import org.springframework.test.web.reactive.server.EntityExchangeResult;
import org.springframework.test.web.reactive.server.FluxExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.server.WebSession;
import reactor.core.publisher.Mono;
import static org.assertj.core.api.Assertions.assertThat;
@@ -101,7 +101,22 @@ public class HttpSecurityTests {
assertThat(result.getResponseCookies().getFirst("SESSION")).isNotNull();
}
@Test
public void basicWhenNoCredentialsThenUnauthorized() {
http.authorizeExchange().anyExchange().authenticated();
WebTestClient client = buildClient();
client
.get()
.uri("/")
.exchange()
.expectStatus().isUnauthorized()
.expectHeader().valueMatches(HttpHeaders.CACHE_CONTROL, ".+")
.expectBody().isEmpty();
}
private WebTestClient buildClient() {
return WebTestClientBuilder.bindToWebFilters(http.build()).build();
WebFilterChainFilter springSecurityFilterChain = WebFilterChainFilter.fromSecurityWebFilterChains(http.build());
return WebTestClientBuilder.bindToWebFilters(springSecurityFilterChain).build();
}
}