Support nested builder in DSL for reactive apps
Fixes: gh-7107
This commit is contained in:
committed by
Rob Winch
parent
ab6440db10
commit
a288ce4b00
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2019 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.
|
||||
@@ -92,6 +92,39 @@ public class AuthorizeExchangeSpecTests {
|
||||
.expectStatus().isUnauthorized();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void antMatchersWhenPatternsInLambdaThenAnyMethod() throws Exception {
|
||||
this.http
|
||||
.csrf(ServerHttpSecurity.CsrfSpec::disable)
|
||||
.authorizeExchange(exchanges ->
|
||||
exchanges
|
||||
.pathMatchers("/a", "/b").denyAll()
|
||||
.anyExchange().permitAll()
|
||||
);
|
||||
|
||||
WebTestClient client = buildClient();
|
||||
|
||||
client.get()
|
||||
.uri("/a")
|
||||
.exchange()
|
||||
.expectStatus().isUnauthorized();
|
||||
|
||||
client.get()
|
||||
.uri("/b")
|
||||
.exchange()
|
||||
.expectStatus().isUnauthorized();
|
||||
|
||||
client.post()
|
||||
.uri("/a")
|
||||
.exchange()
|
||||
.expectStatus().isUnauthorized();
|
||||
|
||||
client.post()
|
||||
.uri("/b")
|
||||
.exchange()
|
||||
.expectStatus().isUnauthorized();
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void antMatchersWhenNoAccessAndAnotherMatcherThenThrowsException() {
|
||||
this.http
|
||||
@@ -117,6 +150,16 @@ public class AuthorizeExchangeSpecTests {
|
||||
this.http.build();
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void buildWhenMatcherDefinedWithNoAccessInLambdaThenThrowsException() throws Exception {
|
||||
this.http
|
||||
.authorizeExchange(exchanges ->
|
||||
exchanges
|
||||
.pathMatchers("/incomplete")
|
||||
);
|
||||
this.http.build();
|
||||
}
|
||||
|
||||
private WebTestClient buildClient() {
|
||||
return WebTestClientBuilder.bindToWebFilters(this.http.build()).build();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2019 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.
|
||||
@@ -74,6 +74,14 @@ public class CorsSpecTests {
|
||||
assertHeaders();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void corsWhenEnabledInLambdaThenAccessControlAllowOriginAndSecurityHeaders() throws Exception {
|
||||
this.http.cors(cors -> cors.configurationSource(this.source));
|
||||
this.expectedHeaders.set("Access-Control-Allow-Origin", "*");
|
||||
this.expectedHeaders.set("X-Frame-Options", "DENY");
|
||||
assertHeaders();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void corsWhenCorsConfigurationSourceBeanThenAccessControlAllowOriginAndSecurityHeaders() {
|
||||
when(this.context.getBeanNamesForType(any(ResolvableType.class))).thenReturn(new String[] {"source"}, new String[0]);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2019 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.
|
||||
@@ -26,6 +26,8 @@ import org.springframework.security.web.server.authorization.HttpStatusServerAcc
|
||||
import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
import static org.springframework.security.config.Customizer.withDefaults;
|
||||
|
||||
/**
|
||||
* @author Denys Ivano
|
||||
* @since 5.0.5
|
||||
@@ -56,6 +58,29 @@ public class ExceptionHandlingSpecTests {
|
||||
.expectHeader().valueMatches("WWW-Authenticate", "Basic.*");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenExceptionHandlingWithDefaultsInLambdaThenDefaultAuthenticationEntryPointUsed()
|
||||
throws Exception {
|
||||
SecurityWebFilterChain securityWebFilter = this.http
|
||||
.authorizeExchange(exchanges ->
|
||||
exchanges
|
||||
.anyExchange().authenticated()
|
||||
)
|
||||
.exceptionHandling(withDefaults())
|
||||
.build();
|
||||
|
||||
WebTestClient client = WebTestClientBuilder
|
||||
.bindToWebFilters(securityWebFilter)
|
||||
.build();
|
||||
|
||||
client
|
||||
.get()
|
||||
.uri("/test")
|
||||
.exchange()
|
||||
.expectStatus().isUnauthorized()
|
||||
.expectHeader().valueMatches("WWW-Authenticate", "Basic.*");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customAuthenticationEntryPoint() {
|
||||
SecurityWebFilterChain securityWebFilter = this.http
|
||||
@@ -80,6 +105,31 @@ public class ExceptionHandlingSpecTests {
|
||||
.expectHeader().valueMatches("Location", ".*");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenCustomAuthenticationEntryPointInLambdaThenCustomAuthenticationEntryPointUsed() throws Exception {
|
||||
SecurityWebFilterChain securityWebFilter = this.http
|
||||
.authorizeExchange(exchanges ->
|
||||
exchanges
|
||||
.anyExchange().authenticated()
|
||||
)
|
||||
.exceptionHandling(exceptionHandling ->
|
||||
exceptionHandling
|
||||
.authenticationEntryPoint(redirectServerAuthenticationEntryPoint("/auth"))
|
||||
)
|
||||
.build();
|
||||
|
||||
WebTestClient client = WebTestClientBuilder
|
||||
.bindToWebFilters(securityWebFilter)
|
||||
.build();
|
||||
|
||||
client
|
||||
.get()
|
||||
.uri("/test")
|
||||
.exchange()
|
||||
.expectStatus().isFound()
|
||||
.expectHeader().valueMatches("Location", ".*");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultAccessDeniedHandler() {
|
||||
SecurityWebFilterChain securityWebFilter = this.http
|
||||
@@ -104,6 +154,30 @@ public class ExceptionHandlingSpecTests {
|
||||
.expectStatus().isForbidden();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenExceptionHandlingWithDefaultsInLambdaThenDefaultAccessDeniedHandlerUsed()
|
||||
throws Exception {
|
||||
SecurityWebFilterChain securityWebFilter = this.http
|
||||
.httpBasic(withDefaults())
|
||||
.authorizeExchange(exchanges ->
|
||||
exchanges
|
||||
.anyExchange().hasRole("ADMIN")
|
||||
)
|
||||
.exceptionHandling(withDefaults())
|
||||
.build();
|
||||
|
||||
WebTestClient client = WebTestClientBuilder
|
||||
.bindToWebFilters(securityWebFilter)
|
||||
.build();
|
||||
|
||||
client
|
||||
.get()
|
||||
.uri("/admin")
|
||||
.headers(headers -> headers.setBasicAuth("user", "password"))
|
||||
.exchange()
|
||||
.expectStatus().isForbidden();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customAccessDeniedHandler() {
|
||||
SecurityWebFilterChain securityWebFilter = this.http
|
||||
@@ -129,6 +203,33 @@ public class ExceptionHandlingSpecTests {
|
||||
.expectStatus().isBadRequest();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenCustomAccessDeniedHandlerInLambdaThenCustomAccessDeniedHandlerUsed()
|
||||
throws Exception {
|
||||
SecurityWebFilterChain securityWebFilter = this.http
|
||||
.httpBasic(withDefaults())
|
||||
.authorizeExchange(exchanges ->
|
||||
exchanges
|
||||
.anyExchange().hasRole("ADMIN")
|
||||
)
|
||||
.exceptionHandling(exceptionHandling ->
|
||||
exceptionHandling
|
||||
.accessDeniedHandler(httpStatusServerAccessDeniedHandler(HttpStatus.BAD_REQUEST))
|
||||
)
|
||||
.build();
|
||||
|
||||
WebTestClient client = WebTestClientBuilder
|
||||
.bindToWebFilters(securityWebFilter)
|
||||
.build();
|
||||
|
||||
client
|
||||
.get()
|
||||
.uri("/admin")
|
||||
.headers(headers -> headers.setBasicAuth("user", "password"))
|
||||
.exchange()
|
||||
.expectStatus().isBadRequest();
|
||||
}
|
||||
|
||||
private ServerAuthenticationEntryPoint redirectServerAuthenticationEntryPoint(String location) {
|
||||
return new RedirectServerAuthenticationEntryPoint(location);
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
import static org.springframework.security.config.Customizer.withDefaults;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
@@ -96,6 +97,49 @@ public class FormLoginTests {
|
||||
.assertLogout();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void formLoginWhenDefaultsInLambdaThenCreatesDefaultLoginPage() throws Exception {
|
||||
SecurityWebFilterChain securityWebFilter = this.http
|
||||
.authorizeExchange(exchanges ->
|
||||
exchanges
|
||||
.anyExchange().authenticated()
|
||||
)
|
||||
.formLogin(withDefaults())
|
||||
.build();
|
||||
|
||||
WebTestClient webTestClient = WebTestClientBuilder
|
||||
.bindToWebFilters(securityWebFilter)
|
||||
.build();
|
||||
|
||||
WebDriver driver = WebTestClientHtmlUnitDriverBuilder
|
||||
.webTestClientSetup(webTestClient)
|
||||
.build();
|
||||
|
||||
DefaultLoginPage loginPage = HomePage.to(driver, DefaultLoginPage.class)
|
||||
.assertAt();
|
||||
|
||||
loginPage = loginPage.loginForm()
|
||||
.username("user")
|
||||
.password("invalid")
|
||||
.submit(DefaultLoginPage.class)
|
||||
.assertError();
|
||||
|
||||
HomePage homePage = loginPage.loginForm()
|
||||
.username("user")
|
||||
.password("password")
|
||||
.submit(HomePage.class);
|
||||
|
||||
homePage.assertAt();
|
||||
|
||||
loginPage = DefaultLogoutPage.to(driver)
|
||||
.assertAt()
|
||||
.logout();
|
||||
|
||||
loginPage
|
||||
.assertAt()
|
||||
.assertLogout();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customLoginPage() {
|
||||
SecurityWebFilterChain securityWebFilter = this.http
|
||||
@@ -128,6 +172,40 @@ public class FormLoginTests {
|
||||
homePage.assertAt();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void formLoginWhenCustomLoginPageInLambdaThenUsed() throws Exception {
|
||||
SecurityWebFilterChain securityWebFilter = this.http
|
||||
.authorizeExchange(exchanges ->
|
||||
exchanges
|
||||
.pathMatchers("/login").permitAll()
|
||||
.anyExchange().authenticated()
|
||||
)
|
||||
.formLogin(formLogin ->
|
||||
formLogin
|
||||
.loginPage("/login")
|
||||
)
|
||||
.build();
|
||||
|
||||
WebTestClient webTestClient = WebTestClient
|
||||
.bindToController(new CustomLoginPageController(), new WebTestClientBuilder.Http200RestController())
|
||||
.webFilter(new WebFilterChainProxy(securityWebFilter))
|
||||
.build();
|
||||
|
||||
WebDriver driver = WebTestClientHtmlUnitDriverBuilder
|
||||
.webTestClientSetup(webTestClient)
|
||||
.build();
|
||||
|
||||
CustomLoginPage loginPage = HomePage.to(driver, CustomLoginPage.class)
|
||||
.assertAt();
|
||||
|
||||
HomePage homePage = loginPage.loginForm()
|
||||
.username("user")
|
||||
.password("password")
|
||||
.submit(HomePage.class);
|
||||
|
||||
homePage.assertAt();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticationSuccess() {
|
||||
SecurityWebFilterChain securityWebFilter = this.http
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2019 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.
|
||||
@@ -39,6 +39,7 @@ import org.springframework.test.web.reactive.server.FluxExchangeResult;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
|
||||
import static org.springframework.security.config.Customizer.withDefaults;
|
||||
|
||||
/**
|
||||
* Tests for {@link ServerHttpSecurity.HeaderSpec}.
|
||||
@@ -49,7 +50,7 @@ import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
|
||||
*/
|
||||
public class HeaderSpecTests {
|
||||
|
||||
private ServerHttpSecurity.HeaderSpec headers = ServerHttpSecurity.http().headers();
|
||||
private ServerHttpSecurity http = ServerHttpSecurity.http();
|
||||
|
||||
private HttpHeaders expectedHeaders = new HttpHeaders();
|
||||
|
||||
@@ -72,14 +73,23 @@ public class HeaderSpecTests {
|
||||
public void headersWhenDisableThenNoSecurityHeaders() {
|
||||
new HashSet<>(this.expectedHeaders.keySet()).forEach(this::expectHeaderNamesNotPresent);
|
||||
|
||||
this.headers.disable();
|
||||
this.http.headers().disable();
|
||||
|
||||
assertHeaders();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headersWhenDisableInLambdaThenNoSecurityHeaders() throws Exception {
|
||||
new HashSet<>(this.expectedHeaders.keySet()).forEach(this::expectHeaderNamesNotPresent);
|
||||
|
||||
this.http.headers(headers -> headers.disable());
|
||||
|
||||
assertHeaders();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headersWhenDisableAndInvokedExplicitlyThenDefautsUsed() {
|
||||
this.headers.disable()
|
||||
this.http.headers().disable()
|
||||
.headers();
|
||||
|
||||
assertHeaders();
|
||||
@@ -87,13 +97,34 @@ public class HeaderSpecTests {
|
||||
|
||||
@Test
|
||||
public void headersWhenDefaultsThenAllDefaultsWritten() {
|
||||
this.http.headers();
|
||||
|
||||
assertHeaders();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headersWhenDefaultsInLambdaThenAllDefaultsWritten() throws Exception {
|
||||
this.http.headers(withDefaults());
|
||||
|
||||
assertHeaders();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headersWhenCacheDisableThenCacheNotWritten() {
|
||||
expectHeaderNamesNotPresent(HttpHeaders.CACHE_CONTROL, HttpHeaders.PRAGMA, HttpHeaders.EXPIRES);
|
||||
this.headers.cache().disable();
|
||||
this.http.headers().cache().disable();
|
||||
|
||||
assertHeaders();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void headersWhenCacheDisableInLambdaThenCacheNotWritten() throws Exception {
|
||||
expectHeaderNamesNotPresent(HttpHeaders.CACHE_CONTROL, HttpHeaders.PRAGMA, HttpHeaders.EXPIRES);
|
||||
this.http
|
||||
.headers(headers ->
|
||||
headers.cache(cache -> cache.disable())
|
||||
);
|
||||
|
||||
assertHeaders();
|
||||
}
|
||||
@@ -101,7 +132,18 @@ public class HeaderSpecTests {
|
||||
@Test
|
||||
public void headersWhenContentOptionsDisableThenContentTypeOptionsNotWritten() {
|
||||
expectHeaderNamesNotPresent(ContentTypeOptionsServerHttpHeadersWriter.X_CONTENT_OPTIONS);
|
||||
this.headers.contentTypeOptions().disable();
|
||||
this.http.headers().contentTypeOptions().disable();
|
||||
|
||||
assertHeaders();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headersWhenContentOptionsDisableInLambdaThenContentTypeOptionsNotWritten() throws Exception {
|
||||
expectHeaderNamesNotPresent(ContentTypeOptionsServerHttpHeadersWriter.X_CONTENT_OPTIONS);
|
||||
this.http
|
||||
.headers(headers ->
|
||||
headers.contentTypeOptions(contentTypeOptions -> contentTypeOptions.disable())
|
||||
);
|
||||
|
||||
assertHeaders();
|
||||
}
|
||||
@@ -109,7 +151,18 @@ public class HeaderSpecTests {
|
||||
@Test
|
||||
public void headersWhenHstsDisableThenHstsNotWritten() {
|
||||
expectHeaderNamesNotPresent(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY);
|
||||
this.headers.hsts().disable();
|
||||
this.http.headers().hsts().disable();
|
||||
|
||||
assertHeaders();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headersWhenHstsDisableInLambdaThenHstsNotWritten() throws Exception {
|
||||
expectHeaderNamesNotPresent(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY);
|
||||
this.http
|
||||
.headers(headers ->
|
||||
headers.hsts(hsts -> hsts.disable())
|
||||
);
|
||||
|
||||
assertHeaders();
|
||||
}
|
||||
@@ -118,28 +171,73 @@ public class HeaderSpecTests {
|
||||
public void headersWhenHstsCustomThenCustomHstsWritten() {
|
||||
this.expectedHeaders.remove(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY);
|
||||
this.expectedHeaders.add(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, "max-age=60");
|
||||
this.headers.hsts()
|
||||
this.http.headers().hsts()
|
||||
.maxAge(Duration.ofSeconds(60))
|
||||
.includeSubdomains(false);
|
||||
|
||||
assertHeaders();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headersWhenHstsCustomInLambdaThenCustomHstsWritten() throws Exception {
|
||||
this.expectedHeaders.remove(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY);
|
||||
this.expectedHeaders.add(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, "max-age=60");
|
||||
this.http
|
||||
.headers(headers ->
|
||||
headers
|
||||
.hsts(hsts ->
|
||||
hsts
|
||||
.maxAge(Duration.ofSeconds(60))
|
||||
.includeSubdomains(false)
|
||||
)
|
||||
);
|
||||
|
||||
assertHeaders();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headersWhenHstsCustomWithPreloadThenCustomHstsWritten() {
|
||||
this.expectedHeaders.remove(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY);
|
||||
this.expectedHeaders.add(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, "max-age=60 ; includeSubDomains ; preload");
|
||||
this.headers.hsts()
|
||||
this.http.headers().hsts()
|
||||
.maxAge(Duration.ofSeconds(60))
|
||||
.preload(true);
|
||||
|
||||
assertHeaders();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headersWhenHstsCustomWithPreloadInLambdaThenCustomHstsWritten() throws Exception {
|
||||
this.expectedHeaders.remove(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY);
|
||||
this.expectedHeaders.add(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, "max-age=60 ; includeSubDomains ; preload");
|
||||
this.http
|
||||
.headers(headers ->
|
||||
headers
|
||||
.hsts(hsts ->
|
||||
hsts
|
||||
.maxAge(Duration.ofSeconds(60))
|
||||
.preload(true)
|
||||
)
|
||||
);
|
||||
|
||||
assertHeaders();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headersWhenFrameOptionsDisableThenFrameOptionsNotWritten() {
|
||||
expectHeaderNamesNotPresent(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS);
|
||||
this.headers.frameOptions().disable();
|
||||
this.http.headers().frameOptions().disable();
|
||||
|
||||
assertHeaders();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headersWhenFrameOptionsDisableInLambdaThenFrameOptionsNotWritten() throws Exception {
|
||||
expectHeaderNamesNotPresent(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS);
|
||||
this.http
|
||||
.headers(headers ->
|
||||
headers.frameOptions(frameOptions -> frameOptions.disable())
|
||||
);
|
||||
|
||||
assertHeaders();
|
||||
}
|
||||
@@ -147,17 +245,43 @@ public class HeaderSpecTests {
|
||||
@Test
|
||||
public void headersWhenFrameOptionsModeThenFrameOptionsCustomMode() {
|
||||
this.expectedHeaders.set(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS, "SAMEORIGIN");
|
||||
this.headers
|
||||
this.http.headers()
|
||||
.frameOptions()
|
||||
.mode(XFrameOptionsServerHttpHeadersWriter.Mode.SAMEORIGIN);
|
||||
|
||||
assertHeaders();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headersWhenFrameOptionsModeInLambdaThenFrameOptionsCustomMode() throws Exception {
|
||||
this.expectedHeaders.set(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS, "SAMEORIGIN");
|
||||
this.http
|
||||
.headers(headers ->
|
||||
headers
|
||||
.frameOptions(frameOptions ->
|
||||
frameOptions
|
||||
.mode(XFrameOptionsServerHttpHeadersWriter.Mode.SAMEORIGIN)
|
||||
)
|
||||
);
|
||||
|
||||
assertHeaders();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headersWhenXssProtectionDisableThenXssProtectionNotWritten() {
|
||||
expectHeaderNamesNotPresent("X-Xss-Protection");
|
||||
this.headers.xssProtection().disable();
|
||||
this.http.headers().xssProtection().disable();
|
||||
|
||||
assertHeaders();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headersWhenXssProtectionDisableInLambdaThenXssProtectionNotWritten() throws Exception {
|
||||
expectHeaderNamesNotPresent("X-Xss-Protection");
|
||||
this.http
|
||||
.headers(headers ->
|
||||
headers.xssProtection(xssProtection -> xssProtection.disable())
|
||||
);
|
||||
|
||||
assertHeaders();
|
||||
}
|
||||
@@ -168,7 +292,7 @@ public class HeaderSpecTests {
|
||||
this.expectedHeaders.add(FeaturePolicyServerHttpHeadersWriter.FEATURE_POLICY,
|
||||
policyDirectives);
|
||||
|
||||
this.headers.featurePolicy(policyDirectives);
|
||||
this.http.headers().featurePolicy(policyDirectives);
|
||||
|
||||
assertHeaders();
|
||||
}
|
||||
@@ -179,7 +303,39 @@ public class HeaderSpecTests {
|
||||
this.expectedHeaders.add(ContentSecurityPolicyServerHttpHeadersWriter.CONTENT_SECURITY_POLICY,
|
||||
policyDirectives);
|
||||
|
||||
this.headers.contentSecurityPolicy(policyDirectives);
|
||||
this.http.headers().contentSecurityPolicy(policyDirectives);
|
||||
|
||||
assertHeaders();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headersWhenContentSecurityPolicyEnabledWithDefaultsInLambdaThenDefaultPolicyWritten() throws Exception {
|
||||
String expectedPolicyDirectives = "default-src 'self'";
|
||||
this.expectedHeaders.add(ContentSecurityPolicyServerHttpHeadersWriter.CONTENT_SECURITY_POLICY,
|
||||
expectedPolicyDirectives);
|
||||
|
||||
this.http
|
||||
.headers(headers ->
|
||||
headers.contentSecurityPolicy(withDefaults())
|
||||
);
|
||||
|
||||
assertHeaders();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headersWhenContentSecurityPolicyEnabledInLambdaThenContentSecurityPolicyWritten() throws Exception {
|
||||
String policyDirectives = "default-src 'self' *.trusted.com";
|
||||
this.expectedHeaders.add(ContentSecurityPolicyServerHttpHeadersWriter.CONTENT_SECURITY_POLICY,
|
||||
policyDirectives);
|
||||
|
||||
this.http
|
||||
.headers(headers ->
|
||||
headers
|
||||
.contentSecurityPolicy(contentSecurityPolicy ->
|
||||
contentSecurityPolicy
|
||||
.policyDirectives(policyDirectives)
|
||||
)
|
||||
);
|
||||
|
||||
assertHeaders();
|
||||
}
|
||||
@@ -188,7 +344,20 @@ public class HeaderSpecTests {
|
||||
public void headersWhenReferrerPolicyEnabledThenFeaturePolicyWritten() {
|
||||
this.expectedHeaders.add(ReferrerPolicyServerHttpHeadersWriter.REFERRER_POLICY,
|
||||
ReferrerPolicy.NO_REFERRER.getPolicy());
|
||||
this.headers.referrerPolicy();
|
||||
this.http.headers().referrerPolicy();
|
||||
|
||||
assertHeaders();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headersWhenReferrerPolicyEnabledInLambdaThenReferrerPolicyWritten() throws Exception {
|
||||
this.expectedHeaders.add(ReferrerPolicyServerHttpHeadersWriter.REFERRER_POLICY,
|
||||
ReferrerPolicy.NO_REFERRER.getPolicy());
|
||||
this.http
|
||||
.headers(headers ->
|
||||
headers
|
||||
.referrerPolicy(withDefaults())
|
||||
);
|
||||
|
||||
assertHeaders();
|
||||
}
|
||||
@@ -197,7 +366,23 @@ public class HeaderSpecTests {
|
||||
public void headersWhenReferrerPolicyCustomEnabledThenFeaturePolicyCustomWritten() {
|
||||
this.expectedHeaders.add(ReferrerPolicyServerHttpHeadersWriter.REFERRER_POLICY,
|
||||
ReferrerPolicy.NO_REFERRER_WHEN_DOWNGRADE.getPolicy());
|
||||
this.headers.referrerPolicy(ReferrerPolicy.NO_REFERRER_WHEN_DOWNGRADE);
|
||||
this.http.headers().referrerPolicy(ReferrerPolicy.NO_REFERRER_WHEN_DOWNGRADE);
|
||||
|
||||
assertHeaders();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headersWhenReferrerPolicyCustomEnabledInLambdaThenCustomReferrerPolicyWritten() throws Exception {
|
||||
this.expectedHeaders.add(ReferrerPolicyServerHttpHeadersWriter.REFERRER_POLICY,
|
||||
ReferrerPolicy.NO_REFERRER_WHEN_DOWNGRADE.getPolicy());
|
||||
this.http
|
||||
.headers(headers ->
|
||||
headers
|
||||
.referrerPolicy(referrerPolicy ->
|
||||
referrerPolicy
|
||||
.policy(ReferrerPolicy.NO_REFERRER_WHEN_DOWNGRADE)
|
||||
)
|
||||
);
|
||||
|
||||
assertHeaders();
|
||||
}
|
||||
@@ -228,6 +413,6 @@ public class HeaderSpecTests {
|
||||
}
|
||||
|
||||
private WebTestClient buildClient() {
|
||||
return WebTestClientBuilder.bindToWebFilters(this.headers.and().build()).build();
|
||||
return WebTestClientBuilder.bindToWebFilters(this.http.build()).build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2019 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.
|
||||
@@ -33,6 +33,7 @@ import org.springframework.web.reactive.config.EnableWebFlux;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.security.config.Customizer.withDefaults;
|
||||
|
||||
/**
|
||||
* Tests for {@link HttpsRedirectSpecTests}
|
||||
@@ -71,6 +72,17 @@ public class HttpsRedirectSpecTests {
|
||||
.expectHeader().valueEquals(HttpHeaders.LOCATION, "https://localhost");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenInsecureAndRedirectConfiguredInLambdaThenRespondsWithRedirectToSecure() {
|
||||
this.spring.register(RedirectToHttpsInLambdaConfig.class).autowire();
|
||||
|
||||
this.client.get()
|
||||
.uri("http://localhost")
|
||||
.exchange()
|
||||
.expectStatus().isFound()
|
||||
.expectHeader().valueEquals(HttpHeaders.LOCATION, "https://localhost");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenInsecureAndPathRequiresTransportSecurityThenRedirects() {
|
||||
this.spring.register(SometimesRedirectToHttpsConfig.class).autowire();
|
||||
@@ -87,6 +99,22 @@ public class HttpsRedirectSpecTests {
|
||||
.expectHeader().valueEquals(HttpHeaders.LOCATION, "https://localhost:8443/secure");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenInsecureAndPathRequiresTransportSecurityInLambdaThenRedirects() {
|
||||
this.spring.register(SometimesRedirectToHttpsInLambdaConfig.class).autowire();
|
||||
|
||||
this.client.get()
|
||||
.uri("http://localhost:8080")
|
||||
.exchange()
|
||||
.expectStatus().isNotFound();
|
||||
|
||||
this.client.get()
|
||||
.uri("http://localhost:8080/secure")
|
||||
.exchange()
|
||||
.expectStatus().isFound()
|
||||
.expectHeader().valueEquals(HttpHeaders.LOCATION, "https://localhost:8443/secure");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenInsecureAndUsingCustomPortMapperThenRespondsWithRedirectToSecurePort() {
|
||||
this.spring.register(RedirectToHttpsViaCustomPortsConfig.class).autowire();
|
||||
@@ -101,6 +129,20 @@ public class HttpsRedirectSpecTests {
|
||||
.expectHeader().valueEquals(HttpHeaders.LOCATION, "https://localhost:4443");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenInsecureAndUsingCustomPortMapperInLambdaThenRespondsWithRedirectToSecurePort() {
|
||||
this.spring.register(RedirectToHttpsViaCustomPortsInLambdaConfig.class).autowire();
|
||||
|
||||
PortMapper portMapper = this.spring.getContext().getBean(PortMapper.class);
|
||||
when(portMapper.lookupHttpsPort(4080)).thenReturn(4443);
|
||||
|
||||
this.client.get()
|
||||
.uri("http://localhost:4080")
|
||||
.exchange()
|
||||
.expectStatus().isFound()
|
||||
.expectHeader().valueEquals(HttpHeaders.LOCATION, "https://localhost:4443");
|
||||
}
|
||||
|
||||
@EnableWebFlux
|
||||
@EnableWebFluxSecurity
|
||||
static class RedirectToHttpConfig {
|
||||
@@ -115,6 +157,21 @@ public class HttpsRedirectSpecTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@EnableWebFlux
|
||||
@EnableWebFluxSecurity
|
||||
static class RedirectToHttpsInLambdaConfig {
|
||||
@Bean
|
||||
SecurityWebFilterChain springSecurity(ServerHttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.redirectToHttps(withDefaults());
|
||||
// @formatter:on
|
||||
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
|
||||
@EnableWebFlux
|
||||
@EnableWebFluxSecurity
|
||||
static class SometimesRedirectToHttpsConfig {
|
||||
@@ -130,6 +187,24 @@ public class HttpsRedirectSpecTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@EnableWebFlux
|
||||
@EnableWebFluxSecurity
|
||||
static class SometimesRedirectToHttpsInLambdaConfig {
|
||||
@Bean
|
||||
SecurityWebFilterChain springSecurity(ServerHttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.redirectToHttps(redirectToHttps ->
|
||||
redirectToHttps
|
||||
.httpsRedirectWhen(new PathPatternParserServerWebExchangeMatcher("/secure"))
|
||||
);
|
||||
// @formatter:on
|
||||
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
|
||||
@EnableWebFlux
|
||||
@EnableWebFluxSecurity
|
||||
static class RedirectToHttpsViaCustomPortsConfig {
|
||||
@@ -149,4 +224,26 @@ public class HttpsRedirectSpecTests {
|
||||
return mock(PortMapper.class);
|
||||
}
|
||||
}
|
||||
|
||||
@EnableWebFlux
|
||||
@EnableWebFluxSecurity
|
||||
static class RedirectToHttpsViaCustomPortsInLambdaConfig {
|
||||
@Bean
|
||||
SecurityWebFilterChain springSecurity(ServerHttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.redirectToHttps(redirectToHttps ->
|
||||
redirectToHttps
|
||||
.portMapper(portMapper())
|
||||
);
|
||||
// @formatter:on
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PortMapper portMapper() {
|
||||
return mock(PortMapper.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2019 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.
|
||||
@@ -25,6 +25,8 @@ import org.springframework.security.web.server.util.matcher.ServerWebExchangeMat
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import org.springframework.security.test.web.reactive.server.WebTestClientBuilder;
|
||||
|
||||
import static org.springframework.security.config.Customizer.withDefaults;
|
||||
|
||||
/**
|
||||
* @author Shazin Sadakath
|
||||
* @since 5.0
|
||||
@@ -117,4 +119,49 @@ public class LogoutSpecTests {
|
||||
.assertAt()
|
||||
.assertLogout();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void logoutWhenCustomLogoutInLambdaThenCustomLogoutUsed() throws Exception {
|
||||
SecurityWebFilterChain securityWebFilter = this.http
|
||||
.authorizeExchange(authorizeExchange ->
|
||||
authorizeExchange
|
||||
.anyExchange().authenticated()
|
||||
)
|
||||
.formLogin(withDefaults())
|
||||
.logout(logout ->
|
||||
logout
|
||||
.requiresLogout(ServerWebExchangeMatchers.pathMatchers("/custom-logout"))
|
||||
)
|
||||
.build();
|
||||
|
||||
WebTestClient webTestClient = WebTestClientBuilder
|
||||
.bindToWebFilters(securityWebFilter)
|
||||
.build();
|
||||
|
||||
WebDriver driver = WebTestClientHtmlUnitDriverBuilder
|
||||
.webTestClientSetup(webTestClient)
|
||||
.build();
|
||||
|
||||
FormLoginTests.DefaultLoginPage loginPage = FormLoginTests.HomePage.to(driver, FormLoginTests.DefaultLoginPage.class)
|
||||
.assertAt();
|
||||
|
||||
loginPage = loginPage.loginForm()
|
||||
.username("user")
|
||||
.password("invalid")
|
||||
.submit(FormLoginTests.DefaultLoginPage.class)
|
||||
.assertError();
|
||||
|
||||
FormLoginTests.HomePage homePage = loginPage.loginForm()
|
||||
.username("user")
|
||||
.password("password")
|
||||
.submit(FormLoginTests.HomePage.class);
|
||||
|
||||
homePage.assertAt();
|
||||
|
||||
driver.get("http://localhost/custom-logout");
|
||||
|
||||
FormLoginTests.DefaultLoginPage.create(driver)
|
||||
.assertAt()
|
||||
.assertLogout();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,4 +185,48 @@ public class OAuth2ClientSpecTests {
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void oauth2ClientWhenCustomObjectsInLambdaThenUsed() {
|
||||
this.spring.register(ClientRegistrationConfig.class, OAuth2ClientInLambdaCustomConfig.class, AuthorizedClientController.class).autowire();
|
||||
|
||||
OAuth2ClientInLambdaCustomConfig config = this.spring.getContext().getBean(OAuth2ClientInLambdaCustomConfig.class);
|
||||
|
||||
ServerAuthenticationConverter converter = config.authenticationConverter;
|
||||
ReactiveAuthenticationManager manager = config.manager;
|
||||
|
||||
OAuth2AuthorizationExchange exchange = TestOAuth2AuthorizationExchanges.success();
|
||||
OAuth2AccessToken accessToken = TestOAuth2AccessTokens.noScopes();
|
||||
|
||||
OAuth2AuthorizationCodeAuthenticationToken result = new OAuth2AuthorizationCodeAuthenticationToken(this.registration, exchange, accessToken);
|
||||
|
||||
when(converter.convert(any())).thenReturn(Mono.just(new TestingAuthenticationToken("a", "b", "c")));
|
||||
when(manager.authenticate(any())).thenReturn(Mono.just(result));
|
||||
|
||||
this.client.get()
|
||||
.uri("/authorize/oauth2/code/registration-id")
|
||||
.exchange()
|
||||
.expectStatus().is3xxRedirection();
|
||||
|
||||
verify(converter).convert(any());
|
||||
verify(manager).authenticate(any());
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class OAuth2ClientInLambdaCustomConfig {
|
||||
ReactiveAuthenticationManager manager = mock(ReactiveAuthenticationManager.class);
|
||||
|
||||
ServerAuthenticationConverter authenticationConverter = mock(ServerAuthenticationConverter.class);
|
||||
|
||||
@Bean
|
||||
public SecurityWebFilterChain springSecurityFilter(ServerHttpSecurity http) throws Exception {
|
||||
http
|
||||
.oauth2Client(oauth2Client ->
|
||||
oauth2Client
|
||||
.authenticationConverter(this.authenticationConverter)
|
||||
.authenticationManager(this.manager)
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,6 +261,87 @@ public class OAuth2LoginTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void oauth2LoginWhenCustomObjectsInLambdaThenUsed() {
|
||||
this.spring.register(OAuth2LoginWithSingleClientRegistrations.class,
|
||||
OAuth2LoginMockAuthenticationManagerInLambdaConfig.class).autowire();
|
||||
|
||||
String redirectLocation = "/custom-redirect-location";
|
||||
|
||||
WebTestClient webTestClient = WebTestClientBuilder
|
||||
.bindToWebFilters(this.springSecurity)
|
||||
.build();
|
||||
|
||||
OAuth2LoginMockAuthenticationManagerInLambdaConfig config = this.spring.getContext()
|
||||
.getBean(OAuth2LoginMockAuthenticationManagerInLambdaConfig.class);
|
||||
ServerAuthenticationConverter converter = config.authenticationConverter;
|
||||
ReactiveAuthenticationManager manager = config.manager;
|
||||
ServerWebExchangeMatcher matcher = config.matcher;
|
||||
ServerOAuth2AuthorizationRequestResolver resolver = config.resolver;
|
||||
ServerAuthenticationSuccessHandler successHandler = config.successHandler;
|
||||
|
||||
OAuth2AuthorizationExchange exchange = TestOAuth2AuthorizationExchanges.success();
|
||||
OAuth2User user = TestOAuth2Users.create();
|
||||
OAuth2AccessToken accessToken = TestOAuth2AccessTokens.noScopes();
|
||||
|
||||
OAuth2LoginAuthenticationToken result = new OAuth2LoginAuthenticationToken(github, exchange, user, user.getAuthorities(), accessToken);
|
||||
|
||||
when(converter.convert(any())).thenReturn(Mono.just(new TestingAuthenticationToken("a", "b", "c")));
|
||||
when(manager.authenticate(any())).thenReturn(Mono.just(result));
|
||||
when(matcher.matches(any())).thenReturn(ServerWebExchangeMatcher.MatchResult.match());
|
||||
when(resolver.resolve(any())).thenReturn(Mono.empty());
|
||||
when(successHandler.onAuthenticationSuccess(any(), any())).thenAnswer((Answer<Mono<Void>>) invocation -> {
|
||||
WebFilterExchange webFilterExchange = invocation.getArgument(0);
|
||||
Authentication authentication = invocation.getArgument(1);
|
||||
|
||||
return new RedirectServerAuthenticationSuccessHandler(redirectLocation)
|
||||
.onAuthenticationSuccess(webFilterExchange, authentication);
|
||||
});
|
||||
|
||||
webTestClient.get()
|
||||
.uri("/login/oauth2/code/github")
|
||||
.exchange()
|
||||
.expectStatus().is3xxRedirection()
|
||||
.expectHeader().valueEquals("Location", redirectLocation);
|
||||
|
||||
verify(converter).convert(any());
|
||||
verify(manager).authenticate(any());
|
||||
verify(matcher).matches(any());
|
||||
verify(resolver).resolve(any());
|
||||
verify(successHandler).onAuthenticationSuccess(any(), any());
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class OAuth2LoginMockAuthenticationManagerInLambdaConfig {
|
||||
ReactiveAuthenticationManager manager = mock(ReactiveAuthenticationManager.class);
|
||||
|
||||
ServerAuthenticationConverter authenticationConverter = mock(ServerAuthenticationConverter.class);
|
||||
|
||||
ServerWebExchangeMatcher matcher = mock(ServerWebExchangeMatcher.class);
|
||||
|
||||
ServerOAuth2AuthorizationRequestResolver resolver = mock(ServerOAuth2AuthorizationRequestResolver.class);
|
||||
|
||||
ServerAuthenticationSuccessHandler successHandler = mock(ServerAuthenticationSuccessHandler.class);
|
||||
|
||||
@Bean
|
||||
public SecurityWebFilterChain springSecurityFilter(ServerHttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeExchange(exchanges ->
|
||||
exchanges
|
||||
.anyExchange().authenticated()
|
||||
)
|
||||
.oauth2Login(oauth2Login ->
|
||||
oauth2Login
|
||||
.authenticationConverter(authenticationConverter)
|
||||
.authenticationManager(manager)
|
||||
.authenticationMatcher(matcher)
|
||||
.authorizationRequestResolver(resolver)
|
||||
.authenticationSuccessHandler(successHandler)
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void oauth2LoginWhenCustomBeansThenUsed() {
|
||||
this.spring.register(OAuth2LoginWithMultipleClientRegistrations.class,
|
||||
|
||||
@@ -175,6 +175,27 @@ public class OAuth2ResourceServerSpecTests {
|
||||
.expectHeader().value(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer error=\"invalid_token\""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenValidTokenAndPublicKeyInLambdaThenReturnsOk() {
|
||||
this.spring.register(PublicKeyInLambdaConfig.class, RootController.class).autowire();
|
||||
|
||||
this.client.get()
|
||||
.headers(headers -> headers.setBearerAuth(this.messageReadToken))
|
||||
.exchange()
|
||||
.expectStatus().isOk();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenExpiredTokenAndPublicKeyInLambdaThenReturnsInvalidToken() {
|
||||
this.spring.register(PublicKeyInLambdaConfig.class).autowire();
|
||||
|
||||
this.client.get()
|
||||
.headers(headers -> headers.setBearerAuth(this.expired))
|
||||
.exchange()
|
||||
.expectStatus().isUnauthorized()
|
||||
.expectHeader().value(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer error=\"invalid_token\""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenValidUsingPlaceholderThenReturnsOk() {
|
||||
this.spring.register(PlaceholderConfig.class, RootController.class).autowire();
|
||||
@@ -213,6 +234,18 @@ public class OAuth2ResourceServerSpecTests {
|
||||
.expectStatus().isOk();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenUsingJwkSetUriInLambdaThenConsultsAccordingly() {
|
||||
this.spring.register(JwkSetUriInLambdaConfig.class, RootController.class).autowire();
|
||||
|
||||
MockWebServer mockWebServer = this.spring.getContext().getBean(MockWebServer.class);
|
||||
mockWebServer.enqueue(new MockResponse().setBody(this.jwkSet));
|
||||
|
||||
this.client.get()
|
||||
.headers(headers -> headers.setBearerAuth(this.messageReadTokenWithKid))
|
||||
.exchange()
|
||||
.expectStatus().isOk();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenUsingCustomAuthenticationManagerThenUsesItAccordingly() {
|
||||
@@ -230,6 +263,22 @@ public class OAuth2ResourceServerSpecTests {
|
||||
.expectHeader().value(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer error=\"mock-failure\""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenUsingCustomAuthenticationManagerInLambdaThenUsesItAccordingly() {
|
||||
this.spring.register(CustomAuthenticationManagerInLambdaConfig.class).autowire();
|
||||
|
||||
ReactiveAuthenticationManager authenticationManager = this.spring.getContext().getBean(
|
||||
ReactiveAuthenticationManager.class);
|
||||
when(authenticationManager.authenticate(any(Authentication.class)))
|
||||
.thenReturn(Mono.error(new OAuth2AuthenticationException(new OAuth2Error("mock-failure"))));
|
||||
|
||||
this.client.get()
|
||||
.headers(headers -> headers.setBearerAuth(this.messageReadToken))
|
||||
.exchange()
|
||||
.expectStatus().isUnauthorized()
|
||||
.expectHeader().value(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer error=\"mock-failure\""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenUsingCustomAuthenticationManagerResolverThenUsesItAccordingly() {
|
||||
this.spring.register(CustomAuthenticationManagerResolverConfig.class).autowire();
|
||||
@@ -396,6 +445,18 @@ public class OAuth2ResourceServerSpecTests {
|
||||
.expectStatus().isOk();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void introspectWhenValidAndIntrospectionInLambdaThenReturnsOk() {
|
||||
this.spring.register(IntrospectionInLambdaConfig.class, RootController.class).autowire();
|
||||
this.spring.getContext().getBean(MockWebServer.class)
|
||||
.setDispatcher(requiresAuth(clientId, clientSecret, active));
|
||||
|
||||
this.client.get()
|
||||
.headers(headers -> headers.setBearerAuth(this.messageReadToken))
|
||||
.exchange()
|
||||
.expectStatus().isOk();
|
||||
}
|
||||
|
||||
@EnableWebFlux
|
||||
@EnableWebFluxSecurity
|
||||
static class PublicKeyConfig {
|
||||
@@ -416,6 +477,30 @@ public class OAuth2ResourceServerSpecTests {
|
||||
}
|
||||
}
|
||||
|
||||
@EnableWebFlux
|
||||
@EnableWebFluxSecurity
|
||||
static class PublicKeyInLambdaConfig {
|
||||
@Bean
|
||||
SecurityWebFilterChain springSecurity(ServerHttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.authorizeExchange(exchanges ->
|
||||
exchanges
|
||||
.anyExchange().hasAuthority("SCOPE_message:read")
|
||||
)
|
||||
.oauth2ResourceServer(oauth2ResourceServer ->
|
||||
oauth2ResourceServer
|
||||
.jwt(jwt ->
|
||||
jwt
|
||||
.publicKey(publicKey())
|
||||
)
|
||||
);
|
||||
// @formatter:on
|
||||
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
|
||||
@EnableWebFlux
|
||||
@EnableWebFluxSecurity
|
||||
static class PlaceholderConfig {
|
||||
@@ -469,6 +554,40 @@ public class OAuth2ResourceServerSpecTests {
|
||||
}
|
||||
}
|
||||
|
||||
@EnableWebFlux
|
||||
@EnableWebFluxSecurity
|
||||
static class JwkSetUriInLambdaConfig {
|
||||
private MockWebServer mockWebServer = new MockWebServer();
|
||||
|
||||
@Bean
|
||||
SecurityWebFilterChain springSecurity(ServerHttpSecurity http) throws Exception {
|
||||
String jwkSetUri = mockWebServer().url("/.well-known/jwks.json").toString();
|
||||
|
||||
// @formatter:off
|
||||
http
|
||||
.oauth2ResourceServer(oauth2ResourceServer ->
|
||||
oauth2ResourceServer
|
||||
.jwt(jwt ->
|
||||
jwt
|
||||
.jwkSetUri(jwkSetUri)
|
||||
)
|
||||
);
|
||||
// @formatter:on
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
MockWebServer mockWebServer() {
|
||||
return this.mockWebServer;
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
void shutdown() throws IOException {
|
||||
this.mockWebServer.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@EnableWebFlux
|
||||
@EnableWebFluxSecurity
|
||||
static class CustomDecoderConfig {
|
||||
@@ -531,6 +650,31 @@ public class OAuth2ResourceServerSpecTests {
|
||||
}
|
||||
}
|
||||
|
||||
@EnableWebFlux
|
||||
@EnableWebFluxSecurity
|
||||
static class CustomAuthenticationManagerInLambdaConfig {
|
||||
@Bean
|
||||
SecurityWebFilterChain springSecurity(ServerHttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.oauth2ResourceServer(oauth2ResourceServer ->
|
||||
oauth2ResourceServer
|
||||
.jwt(jwt ->
|
||||
jwt
|
||||
.authenticationManager(authenticationManager())
|
||||
)
|
||||
);
|
||||
// @formatter:on
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
ReactiveAuthenticationManager authenticationManager() {
|
||||
return mock(ReactiveAuthenticationManager.class);
|
||||
}
|
||||
}
|
||||
|
||||
@EnableWebFlux
|
||||
@EnableWebFluxSecurity
|
||||
static class CustomAuthenticationManagerResolverConfig {
|
||||
@@ -670,6 +814,41 @@ public class OAuth2ResourceServerSpecTests {
|
||||
}
|
||||
}
|
||||
|
||||
@EnableWebFlux
|
||||
@EnableWebFluxSecurity
|
||||
static class IntrospectionInLambdaConfig {
|
||||
private MockWebServer mockWebServer = new MockWebServer();
|
||||
|
||||
@Bean
|
||||
SecurityWebFilterChain springSecurity(ServerHttpSecurity http) throws Exception {
|
||||
String introspectionUri = mockWebServer().url("/introspect").toString();
|
||||
|
||||
// @formatter:off
|
||||
http
|
||||
.oauth2ResourceServer(oauth2ResourceServer ->
|
||||
oauth2ResourceServer
|
||||
.opaqueToken(opaqueToken ->
|
||||
opaqueToken
|
||||
.introspectionUri(introspectionUri)
|
||||
.introspectionClientCredentials("client", "secret")
|
||||
)
|
||||
);
|
||||
// @formatter:on
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
MockWebServer mockWebServer() {
|
||||
return this.mockWebServer;
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
void shutdown() throws IOException {
|
||||
this.mockWebServer.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@RestController
|
||||
static class RootController {
|
||||
@GetMapping
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.security.config.Customizer.withDefaults;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
@@ -103,6 +104,40 @@ public class RequestCacheTests {
|
||||
securedPage.assertAt();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenCustomRequestCacheInLambdaThenCustomCacheUsed() throws Exception {
|
||||
SecurityWebFilterChain securityWebFilter = this.http
|
||||
.authorizeExchange(authorizeExchange ->
|
||||
authorizeExchange
|
||||
.anyExchange().authenticated()
|
||||
)
|
||||
.formLogin(withDefaults())
|
||||
.requestCache(requestCache ->
|
||||
requestCache
|
||||
.requestCache(NoOpServerRequestCache.getInstance())
|
||||
)
|
||||
.build();
|
||||
|
||||
WebTestClient webTestClient = WebTestClient
|
||||
.bindToController(new SecuredPageController(), new WebTestClientBuilder.Http200RestController())
|
||||
.webFilter(new WebFilterChainProxy(securityWebFilter))
|
||||
.build();
|
||||
|
||||
WebDriver driver = WebTestClientHtmlUnitDriverBuilder
|
||||
.webTestClientSetup(webTestClient)
|
||||
.build();
|
||||
|
||||
DefaultLoginPage loginPage = SecuredPage.to(driver, DefaultLoginPage.class)
|
||||
.assertAt();
|
||||
|
||||
HomePage securedPage = loginPage.loginForm()
|
||||
.username("user")
|
||||
.password("password")
|
||||
.submit(HomePage.class);
|
||||
|
||||
securedPage.assertAt();
|
||||
}
|
||||
|
||||
public static class SecuredPage {
|
||||
private WebDriver driver;
|
||||
|
||||
|
||||
@@ -20,8 +20,10 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.security.config.Customizer.withDefaults;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -36,6 +38,7 @@ import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor;
|
||||
import org.springframework.security.web.server.authentication.ServerX509AuthenticationConverter;
|
||||
import reactor.core.publisher.Mono;
|
||||
@@ -236,6 +239,19 @@ public class ServerHttpSecurityTests {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenAnonymousConfiguredThenAuthenticationIsAnonymous() throws Exception {
|
||||
SecurityWebFilterChain securityFilterChain = this.http.anonymous(withDefaults()).build();
|
||||
WebTestClient client = WebTestClientBuilder.bindToControllerAndWebFilters(AnonymousAuthenticationWebFilterTests.HttpMeController.class,
|
||||
securityFilterChain).build();
|
||||
|
||||
client.get()
|
||||
.uri("/me")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody(String.class).isEqualTo("anonymousUser");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void basicWithAnonymous() {
|
||||
given(this.authenticationManager.authenticate(any())).willReturn(Mono.just(new TestingAuthenticationToken("rob", "rob", "ROLE_USER", "ROLE_ADMIN")));
|
||||
@@ -283,6 +299,31 @@ public class ServerHttpSecurityTests {
|
||||
assertThat(result.getResponseCookies().getFirst("SESSION")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenBasicWithRealmNameInLambdaThenRealmNameUsed() throws Exception {
|
||||
this.http.securityContextRepository(new WebSessionServerSecurityContextRepository());
|
||||
HttpBasicServerAuthenticationEntryPoint authenticationEntryPoint = new HttpBasicServerAuthenticationEntryPoint();
|
||||
authenticationEntryPoint.setRealm("myrealm");
|
||||
this.http.httpBasic(httpBasic ->
|
||||
httpBasic.authenticationEntryPoint(authenticationEntryPoint)
|
||||
);
|
||||
this.http.authenticationManager(this.authenticationManager);
|
||||
ServerHttpSecurity.AuthorizeExchangeSpec authorize = this.http.authorizeExchange();
|
||||
authorize.anyExchange().authenticated();
|
||||
|
||||
WebTestClient client = buildClient();
|
||||
|
||||
EntityExchangeResult<String> result = client.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectStatus().isUnauthorized()
|
||||
.expectHeader().value(HttpHeaders.WWW_AUTHENTICATE, value -> assertThat(value).contains("myrealm"))
|
||||
.expectBody(String.class)
|
||||
.returnResult();
|
||||
|
||||
assertThat(result.getResponseCookies().getFirst("SESSION")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void basicWithCustomAuthenticationManager() {
|
||||
ReactiveAuthenticationManager customAuthenticationManager = mock(ReactiveAuthenticationManager.class);
|
||||
@@ -302,6 +343,31 @@ public class ServerHttpSecurityTests {
|
||||
verifyZeroInteractions(this.authenticationManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenBasicWithAuthenticationManagerInLambdaThenAuthenticationManagerUsed() throws Exception {
|
||||
ReactiveAuthenticationManager customAuthenticationManager = mock(ReactiveAuthenticationManager.class);
|
||||
given(customAuthenticationManager.authenticate(any()))
|
||||
.willReturn(Mono.just(new TestingAuthenticationToken("rob", "rob", "ROLE_USER", "ROLE_ADMIN")));
|
||||
|
||||
SecurityWebFilterChain securityFilterChain = this.http
|
||||
.httpBasic(httpBasic ->
|
||||
httpBasic.authenticationManager(customAuthenticationManager)
|
||||
)
|
||||
.build();
|
||||
WebFilterChainProxy springSecurityFilterChain = new WebFilterChainProxy(securityFilterChain);
|
||||
WebTestClient client = WebTestClientBuilder.bindToWebFilters(springSecurityFilterChain).build();
|
||||
|
||||
client.get()
|
||||
.uri("/")
|
||||
.headers(headers -> headers.setBasicAuth("rob", "rob"))
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody(String.class).consumeWith(b -> assertThat(b.getResponseBody()).isEqualTo("ok"));
|
||||
|
||||
verifyZeroInteractions(this.authenticationManager);
|
||||
verify(customAuthenticationManager).authenticate(any(Authentication.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void addsX509FilterWhenX509AuthenticationIsConfigured() {
|
||||
@@ -319,6 +385,23 @@ public class ServerHttpSecurityTests {
|
||||
assertThat(x509WebFilter).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void x509WhenCustomizedThenAddsX509Filter() throws Exception {
|
||||
X509PrincipalExtractor mockExtractor = mock(X509PrincipalExtractor.class);
|
||||
ReactiveAuthenticationManager mockAuthenticationManager = mock(ReactiveAuthenticationManager.class);
|
||||
|
||||
this.http.x509(x509 ->
|
||||
x509
|
||||
.principalExtractor(mockExtractor)
|
||||
.authenticationManager(mockAuthenticationManager)
|
||||
);
|
||||
|
||||
SecurityWebFilterChain securityWebFilterChain = this.http.build();
|
||||
WebFilter x509WebFilter = securityWebFilterChain.getWebFilters().filter(this::isX509Filter).blockFirst();
|
||||
|
||||
assertThat(x509WebFilter).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addsX509FilterWhenX509AuthenticationIsConfiguredWithDefaults() {
|
||||
this.http.x509();
|
||||
@@ -329,6 +412,46 @@ public class ServerHttpSecurityTests {
|
||||
assertThat(x509WebFilter).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void x509WhenDefaultsThenAddsX509Filter() throws Exception {
|
||||
this.http.x509(withDefaults());
|
||||
|
||||
SecurityWebFilterChain securityWebFilterChain = this.http.build();
|
||||
WebFilter x509WebFilter = securityWebFilterChain.getWebFilters().filter(this::isX509Filter).blockFirst();
|
||||
|
||||
assertThat(x509WebFilter).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postWhenCsrfDisabledThenPermitted() throws Exception {
|
||||
SecurityWebFilterChain securityFilterChain = this.http.csrf(csrf -> csrf.disable()).build();
|
||||
WebFilterChainProxy springSecurityFilterChain = new WebFilterChainProxy(securityFilterChain);
|
||||
WebTestClient client = WebTestClientBuilder.bindToWebFilters(springSecurityFilterChain).build();
|
||||
|
||||
client.post()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectStatus().isOk();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postWhenCustomCsrfTokenRepositoryThenUsed() throws Exception {
|
||||
ServerCsrfTokenRepository customServerCsrfTokenRepository = mock(ServerCsrfTokenRepository.class);
|
||||
when(customServerCsrfTokenRepository.loadToken(any(ServerWebExchange.class))).thenReturn(Mono.empty());
|
||||
SecurityWebFilterChain securityFilterChain = this.http
|
||||
.csrf(csrf -> csrf.csrfTokenRepository(customServerCsrfTokenRepository))
|
||||
.build();
|
||||
WebFilterChainProxy springSecurityFilterChain = new WebFilterChainProxy(securityFilterChain);
|
||||
WebTestClient client = WebTestClientBuilder.bindToWebFilters(springSecurityFilterChain).build();
|
||||
|
||||
client.post()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectStatus().isForbidden();
|
||||
|
||||
verify(customServerCsrfTokenRepository).loadToken(any());
|
||||
}
|
||||
|
||||
private boolean isX509Filter(WebFilter filter) {
|
||||
try {
|
||||
Object converter = ReflectionTestUtils.getField(filter, "authenticationConverter");
|
||||
|
||||
Reference in New Issue
Block a user