mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-05-31 09:12:14 +00:00
Support nested builder in DSL for reactive apps
Fixes: gh-7107
This commit is contained in:
parent
ab6440db10
commit
a288ce4b00
@ -32,6 +32,7 @@ import java.util.UUID;
|
|||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.springframework.security.config.Customizer;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import reactor.util.context.Context;
|
import reactor.util.context.Context;
|
||||||
|
|
||||||
@ -394,6 +395,48 @@ public class ServerHttpSecurity {
|
|||||||
return this.httpsRedirectSpec;
|
return this.httpsRedirectSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures HTTPS redirection rules. If the default is used:
|
||||||
|
*
|
||||||
|
* <pre class="code">
|
||||||
|
* @Bean
|
||||||
|
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
||||||
|
* http
|
||||||
|
* // ...
|
||||||
|
* .redirectToHttps(withDefaults());
|
||||||
|
* return http.build();
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* Then all non-HTTPS requests will be redirected to HTTPS.
|
||||||
|
*
|
||||||
|
* Typically, all requests should be HTTPS; however, the focus for redirection can also be narrowed:
|
||||||
|
*
|
||||||
|
* <pre class="code">
|
||||||
|
* @Bean
|
||||||
|
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
|
||||||
|
* http
|
||||||
|
* // ...
|
||||||
|
* .redirectToHttps(redirectToHttps ->
|
||||||
|
* redirectToHttps
|
||||||
|
* .httpsRedirectWhen(serverWebExchange ->
|
||||||
|
* serverWebExchange.getRequest().getHeaders().containsKey("X-Requires-Https"))
|
||||||
|
* );
|
||||||
|
* return http.build();
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param httpsRedirectCustomizer the {@link Customizer} to provide more options for
|
||||||
|
* the {@link HttpsRedirectSpec}
|
||||||
|
* @return the {@link ServerHttpSecurity} to customize
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public ServerHttpSecurity redirectToHttps(Customizer<HttpsRedirectSpec> httpsRedirectCustomizer) throws Exception {
|
||||||
|
this.httpsRedirectSpec = new HttpsRedirectSpec();
|
||||||
|
httpsRedirectCustomizer.customize(this.httpsRedirectSpec);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures <a href="https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet">CSRF Protection</a>
|
* Configures <a href="https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet">CSRF Protection</a>
|
||||||
* which is enabled by default. You can disable it using:
|
* which is enabled by default. You can disable it using:
|
||||||
@ -436,6 +479,56 @@ public class ServerHttpSecurity {
|
|||||||
return this.csrf;
|
return this.csrf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures <a href="https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet">CSRF Protection</a>
|
||||||
|
* which is enabled by default. You can disable it using:
|
||||||
|
*
|
||||||
|
* <pre class="code">
|
||||||
|
* @Bean
|
||||||
|
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
|
||||||
|
* http
|
||||||
|
* // ...
|
||||||
|
* .csrf(csrf ->
|
||||||
|
* csrf.disabled()
|
||||||
|
* );
|
||||||
|
* return http.build();
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* Additional configuration options can be seen below:
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* <pre class="code">
|
||||||
|
* @Bean
|
||||||
|
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
|
||||||
|
* http
|
||||||
|
* // ...
|
||||||
|
* .csrf(csrf ->
|
||||||
|
* csrf
|
||||||
|
* // Handle CSRF failures
|
||||||
|
* .accessDeniedHandler(accessDeniedHandler)
|
||||||
|
* // Custom persistence of CSRF Token
|
||||||
|
* .csrfTokenRepository(csrfTokenRepository)
|
||||||
|
* // custom matching when CSRF protection is enabled
|
||||||
|
* .requireCsrfProtectionMatcher(matcher)
|
||||||
|
* );
|
||||||
|
* return http.build();
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param csrfCustomizer the {@link Customizer} to provide more options for
|
||||||
|
* the {@link CsrfSpec}
|
||||||
|
* @return the {@link ServerHttpSecurity} to customize
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public ServerHttpSecurity csrf(Customizer<CsrfSpec> csrfCustomizer) throws Exception {
|
||||||
|
if (this.csrf == null) {
|
||||||
|
this.csrf = new CsrfSpec();
|
||||||
|
}
|
||||||
|
csrfCustomizer.customize(this.csrf);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures CORS headers. By default if a {@link CorsConfigurationSource} Bean is found, it will be used
|
* Configures CORS headers. By default if a {@link CorsConfigurationSource} Bean is found, it will be used
|
||||||
* to create a {@link CorsWebFilter}. If {@link CorsSpec#configurationSource(CorsConfigurationSource)} is invoked
|
* to create a {@link CorsWebFilter}. If {@link CorsSpec#configurationSource(CorsConfigurationSource)} is invoked
|
||||||
@ -449,6 +542,24 @@ public class ServerHttpSecurity {
|
|||||||
return this.cors;
|
return this.cors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures CORS headers. By default if a {@link CorsConfigurationSource} Bean is found, it will be used
|
||||||
|
* to create a {@link CorsWebFilter}. If {@link CorsSpec#configurationSource(CorsConfigurationSource)} is invoked
|
||||||
|
* it will be used instead. If neither has been configured, the Cors configuration will do nothing.
|
||||||
|
*
|
||||||
|
* @param corsCustomizer the {@link Customizer} to provide more options for
|
||||||
|
* the {@link CorsSpec}
|
||||||
|
* @return the {@link ServerHttpSecurity} to customize
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public ServerHttpSecurity cors(Customizer<CorsSpec> corsCustomizer) throws Exception {
|
||||||
|
if (this.cors == null) {
|
||||||
|
this.cors = new CorsSpec();
|
||||||
|
}
|
||||||
|
corsCustomizer.customize(this.cors);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enables and Configures anonymous authentication. Anonymous Authentication is disabled by default.
|
* Enables and Configures anonymous authentication. Anonymous Authentication is disabled by default.
|
||||||
*
|
*
|
||||||
@ -473,6 +584,36 @@ public class ServerHttpSecurity {
|
|||||||
return this.anonymous;
|
return this.anonymous;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables and Configures anonymous authentication. Anonymous Authentication is disabled by default.
|
||||||
|
*
|
||||||
|
* <pre class="code">
|
||||||
|
* @Bean
|
||||||
|
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
|
||||||
|
* http
|
||||||
|
* // ...
|
||||||
|
* .anonymous(anonymous ->
|
||||||
|
* anonymous
|
||||||
|
* .key("key")
|
||||||
|
* .authorities("ROLE_ANONYMOUS")
|
||||||
|
* );
|
||||||
|
* return http.build();
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param anonymousCustomizer the {@link Customizer} to provide more options for
|
||||||
|
* the {@link AnonymousSpec}
|
||||||
|
* @return the {@link ServerHttpSecurity} to customize
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public ServerHttpSecurity anonymous(Customizer<AnonymousSpec> anonymousCustomizer) throws Exception {
|
||||||
|
if (this.anonymous == null) {
|
||||||
|
this.anonymous = new AnonymousSpec();
|
||||||
|
}
|
||||||
|
anonymousCustomizer.customize(this.anonymous);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures CORS support within Spring Security. This ensures that the {@link CorsWebFilter} is place in the
|
* Configures CORS support within Spring Security. This ensures that the {@link CorsWebFilter} is place in the
|
||||||
* correct order.
|
* correct order.
|
||||||
@ -560,6 +701,38 @@ public class ServerHttpSecurity {
|
|||||||
return this.httpBasic;
|
return this.httpBasic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures HTTP Basic authentication. An example configuration is provided below:
|
||||||
|
*
|
||||||
|
* <pre class="code">
|
||||||
|
* @Bean
|
||||||
|
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
|
||||||
|
* http
|
||||||
|
* // ...
|
||||||
|
* .httpBasic(httpBasic ->
|
||||||
|
* httpBasic
|
||||||
|
* // used for authenticating the credentials
|
||||||
|
* .authenticationManager(authenticationManager)
|
||||||
|
* // Custom persistence of the authentication
|
||||||
|
* .securityContextRepository(securityContextRepository)
|
||||||
|
* );
|
||||||
|
* return http.build();
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param httpBasicCustomizer the {@link Customizer} to provide more options for
|
||||||
|
* the {@link HttpBasicSpec}
|
||||||
|
* @return the {@link ServerHttpSecurity} to customize
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public ServerHttpSecurity httpBasic(Customizer<HttpBasicSpec> httpBasicCustomizer) throws Exception {
|
||||||
|
if (this.httpBasic == null) {
|
||||||
|
this.httpBasic = new HttpBasicSpec();
|
||||||
|
}
|
||||||
|
httpBasicCustomizer.customize(this.httpBasic);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures form based authentication. An example configuration is provided below:
|
* Configures form based authentication. An example configuration is provided below:
|
||||||
*
|
*
|
||||||
@ -590,6 +763,42 @@ public class ServerHttpSecurity {
|
|||||||
return this.formLogin;
|
return this.formLogin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures form based authentication. An example configuration is provided below:
|
||||||
|
*
|
||||||
|
* <pre class="code">
|
||||||
|
* @Bean
|
||||||
|
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
|
||||||
|
* http
|
||||||
|
* // ...
|
||||||
|
* .formLogin(formLogin ->
|
||||||
|
* formLogin
|
||||||
|
* // used for authenticating the credentials
|
||||||
|
* .authenticationManager(authenticationManager)
|
||||||
|
* // Custom persistence of the authentication
|
||||||
|
* .securityContextRepository(securityContextRepository)
|
||||||
|
* // expect a log in page at "/authenticate"
|
||||||
|
* // a POST "/authenticate" is where authentication occurs
|
||||||
|
* // error page at "/authenticate?error"
|
||||||
|
* .loginPage("/authenticate")
|
||||||
|
* );
|
||||||
|
* return http.build();
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param formLoginCustomizer the {@link Customizer} to provide more options for
|
||||||
|
* the {@link FormLoginSpec}
|
||||||
|
* @return the {@link ServerHttpSecurity} to customize
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public ServerHttpSecurity formLogin(Customizer<FormLoginSpec> formLoginCustomizer) throws Exception {
|
||||||
|
if (this.formLogin == null) {
|
||||||
|
this.formLogin = new FormLoginSpec();
|
||||||
|
}
|
||||||
|
formLoginCustomizer.customize(this.formLogin);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures x509 authentication using a certificate provided by a client.
|
* Configures x509 authentication using a certificate provided by a client.
|
||||||
*
|
*
|
||||||
@ -619,6 +828,39 @@ public class ServerHttpSecurity {
|
|||||||
return this.x509;
|
return this.x509;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures x509 authentication using a certificate provided by a client.
|
||||||
|
*
|
||||||
|
* <pre class="code">
|
||||||
|
* @Bean
|
||||||
|
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
|
||||||
|
* http
|
||||||
|
* .x509(x509 ->
|
||||||
|
* x509
|
||||||
|
* .authenticationManager(authenticationManager)
|
||||||
|
* .principalExtractor(principalExtractor)
|
||||||
|
* );
|
||||||
|
* return http.build();
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* Note that if extractor is not specified, {@link SubjectDnX509PrincipalExtractor} will be used.
|
||||||
|
* If authenticationManager is not specified, {@link ReactivePreAuthenticatedAuthenticationManager} will be used.
|
||||||
|
*
|
||||||
|
* @since 5.2
|
||||||
|
* @param x509Customizer the {@link Customizer} to provide more options for
|
||||||
|
* the {@link X509Spec}
|
||||||
|
* @return the {@link ServerHttpSecurity} to customize
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public ServerHttpSecurity x509(Customizer<X509Spec> x509Customizer) throws Exception {
|
||||||
|
if (this.x509 == null) {
|
||||||
|
this.x509 = new X509Spec();
|
||||||
|
}
|
||||||
|
x509Customizer.customize(this.x509);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures X509 authentication
|
* Configures X509 authentication
|
||||||
*
|
*
|
||||||
@ -702,6 +944,36 @@ public class ServerHttpSecurity {
|
|||||||
return this.oauth2Login;
|
return this.oauth2Login;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures authentication support using an OAuth 2.0 and/or OpenID Connect 1.0 Provider.
|
||||||
|
*
|
||||||
|
* <pre class="code">
|
||||||
|
* @Bean
|
||||||
|
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
|
||||||
|
* http
|
||||||
|
* // ...
|
||||||
|
* .oauth2Login(oauth2Login ->
|
||||||
|
* oauth2Login
|
||||||
|
* .authenticationConverter(authenticationConverter)
|
||||||
|
* .authenticationManager(manager)
|
||||||
|
* );
|
||||||
|
* return http.build();
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param oauth2LoginCustomizer the {@link Customizer} to provide more options for
|
||||||
|
* the {@link OAuth2LoginSpec}
|
||||||
|
* @return the {@link ServerHttpSecurity} to customize
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public ServerHttpSecurity oauth2Login(Customizer<OAuth2LoginSpec> oauth2LoginCustomizer) throws Exception {
|
||||||
|
if (this.oauth2Login == null) {
|
||||||
|
this.oauth2Login = new OAuth2LoginSpec();
|
||||||
|
}
|
||||||
|
oauth2LoginCustomizer.customize(this.oauth2Login);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public class OAuth2LoginSpec {
|
public class OAuth2LoginSpec {
|
||||||
private ReactiveClientRegistrationRepository clientRegistrationRepository;
|
private ReactiveClientRegistrationRepository clientRegistrationRepository;
|
||||||
|
|
||||||
@ -995,6 +1267,36 @@ public class ServerHttpSecurity {
|
|||||||
return this.client;
|
return this.client;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the OAuth2 client.
|
||||||
|
*
|
||||||
|
* <pre class="code">
|
||||||
|
* @Bean
|
||||||
|
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
|
||||||
|
* http
|
||||||
|
* // ...
|
||||||
|
* .oauth2Client(oauth2Client ->
|
||||||
|
* oauth2Client
|
||||||
|
* .clientRegistrationRepository(clientRegistrationRepository)
|
||||||
|
* .authorizedClientRepository(authorizedClientRepository)
|
||||||
|
* );
|
||||||
|
* return http.build();
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param oauth2ClientCustomizer the {@link Customizer} to provide more options for
|
||||||
|
* the {@link OAuth2ClientSpec}
|
||||||
|
* @return the {@link ServerHttpSecurity} to customize
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public ServerHttpSecurity oauth2Client(Customizer<OAuth2ClientSpec> oauth2ClientCustomizer) throws Exception {
|
||||||
|
if (this.client == null) {
|
||||||
|
this.client = new OAuth2ClientSpec();
|
||||||
|
}
|
||||||
|
oauth2ClientCustomizer.customize(this.client);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public class OAuth2ClientSpec {
|
public class OAuth2ClientSpec {
|
||||||
private ReactiveClientRegistrationRepository clientRegistrationRepository;
|
private ReactiveClientRegistrationRepository clientRegistrationRepository;
|
||||||
|
|
||||||
@ -1145,6 +1447,39 @@ public class ServerHttpSecurity {
|
|||||||
return this.resourceServer;
|
return this.resourceServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures OAuth 2.0 Resource Server support.
|
||||||
|
*
|
||||||
|
* <pre class="code">
|
||||||
|
* @Bean
|
||||||
|
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
|
||||||
|
* http
|
||||||
|
* // ...
|
||||||
|
* .oauth2ResourceServer(oauth2ResourceServer ->
|
||||||
|
* oauth2ResourceServer
|
||||||
|
* .jwt(jwt ->
|
||||||
|
* jwt
|
||||||
|
* .publicKey(publicKey())
|
||||||
|
* )
|
||||||
|
* );
|
||||||
|
* return http.build();
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param oauth2ResourceServerCustomizer the {@link Customizer} to provide more options for
|
||||||
|
* the {@link OAuth2ResourceServerSpec}
|
||||||
|
* @return the {@link ServerHttpSecurity} to customize
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public ServerHttpSecurity oauth2ResourceServer(Customizer<OAuth2ResourceServerSpec> oauth2ResourceServerCustomizer)
|
||||||
|
throws Exception {
|
||||||
|
if (this.resourceServer == null) {
|
||||||
|
this.resourceServer = new OAuth2ResourceServerSpec();
|
||||||
|
}
|
||||||
|
oauth2ResourceServerCustomizer.customize(this.resourceServer);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures OAuth2 Resource Server Support
|
* Configures OAuth2 Resource Server Support
|
||||||
*/
|
*/
|
||||||
@ -1228,6 +1563,22 @@ public class ServerHttpSecurity {
|
|||||||
return this.jwt;
|
return this.jwt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables JWT Resource Server support.
|
||||||
|
*
|
||||||
|
* @param jwtCustomizer the {@link Customizer} to provide more options for
|
||||||
|
* the {@link JwtSpec}
|
||||||
|
* @return the {@link OAuth2ResourceServerSpec} to customize
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public OAuth2ResourceServerSpec jwt(Customizer<JwtSpec> jwtCustomizer) throws Exception {
|
||||||
|
if (this.jwt == null) {
|
||||||
|
this.jwt = new JwtSpec();
|
||||||
|
}
|
||||||
|
jwtCustomizer.customize(this.jwt);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enables Opaque Token Resource Server support.
|
* Enables Opaque Token Resource Server support.
|
||||||
*
|
*
|
||||||
@ -1240,6 +1591,22 @@ public class ServerHttpSecurity {
|
|||||||
return this.opaqueToken;
|
return this.opaqueToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables Opaque Token Resource Server support.
|
||||||
|
*
|
||||||
|
* @param opaqueTokenCustomizer the {@link Customizer} to provide more options for
|
||||||
|
* the {@link OpaqueTokenSpec}
|
||||||
|
* @return the {@link OAuth2ResourceServerSpec} to customize
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public OAuth2ResourceServerSpec opaqueToken(Customizer<OpaqueTokenSpec> opaqueTokenCustomizer) throws Exception {
|
||||||
|
if (this.opaqueToken == null) {
|
||||||
|
this.opaqueToken = new OpaqueTokenSpec();
|
||||||
|
}
|
||||||
|
opaqueTokenCustomizer.customize(this.opaqueToken);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
protected void configure(ServerHttpSecurity http) {
|
protected void configure(ServerHttpSecurity http) {
|
||||||
this.bearerTokenServerWebExchangeMatcher
|
this.bearerTokenServerWebExchangeMatcher
|
||||||
.setBearerTokenConverter(this.bearerTokenConverter);
|
.setBearerTokenConverter(this.bearerTokenConverter);
|
||||||
@ -1561,6 +1928,58 @@ public class ServerHttpSecurity {
|
|||||||
return this.headers;
|
return this.headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures HTTP Response Headers. The default headers are:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* Cache-Control: no-cache, no-store, max-age=0, must-revalidate
|
||||||
|
* Pragma: no-cache
|
||||||
|
* Expires: 0
|
||||||
|
* X-Content-Type-Options: nosniff
|
||||||
|
* Strict-Transport-Security: max-age=31536000 ; includeSubDomains
|
||||||
|
* X-Frame-Options: DENY
|
||||||
|
* X-XSS-Protection: 1; mode=block
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* such that "Strict-Transport-Security" is only added on secure requests.
|
||||||
|
*
|
||||||
|
* An example configuration is provided below:
|
||||||
|
*
|
||||||
|
* <pre class="code">
|
||||||
|
* @Bean
|
||||||
|
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
|
||||||
|
* http
|
||||||
|
* // ...
|
||||||
|
* .headers(headers ->
|
||||||
|
* headers
|
||||||
|
* // customize frame options to be same origin
|
||||||
|
* .frameOptions(frameOptions ->
|
||||||
|
* frameOptions
|
||||||
|
* .mode(XFrameOptionsServerHttpHeadersWriter.Mode.SAMEORIGIN)
|
||||||
|
* )
|
||||||
|
* // disable cache control
|
||||||
|
* .cache(cache ->
|
||||||
|
* cache
|
||||||
|
* .disable()
|
||||||
|
* )
|
||||||
|
* );
|
||||||
|
* return http.build();
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param headerCustomizer the {@link Customizer} to provide more options for
|
||||||
|
* the {@link HeaderSpec}
|
||||||
|
* @return the {@link ServerHttpSecurity} to customize
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public ServerHttpSecurity headers(Customizer<HeaderSpec> headerCustomizer) throws Exception {
|
||||||
|
if (this.headers == null) {
|
||||||
|
this.headers = new HeaderSpec();
|
||||||
|
}
|
||||||
|
headerCustomizer.customize(this.headers);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures exception handling (i.e. handles when authentication is requested). An example configuration can
|
* Configures exception handling (i.e. handles when authentication is requested). An example configuration can
|
||||||
* be found below:
|
* be found below:
|
||||||
@ -1586,6 +2005,38 @@ public class ServerHttpSecurity {
|
|||||||
return this.exceptionHandling;
|
return this.exceptionHandling;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures exception handling (i.e. handles when authentication is requested). An example configuration can
|
||||||
|
* be found below:
|
||||||
|
*
|
||||||
|
* <pre class="code">
|
||||||
|
* @Bean
|
||||||
|
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
|
||||||
|
* http
|
||||||
|
* // ...
|
||||||
|
* .exceptionHandling(exceptionHandling ->
|
||||||
|
* exceptionHandling
|
||||||
|
* // customize how to request for authentication
|
||||||
|
* .authenticationEntryPoint(entryPoint)
|
||||||
|
* );
|
||||||
|
* return http.build();
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param exceptionHandlingCustomizer the {@link Customizer} to provide more options for
|
||||||
|
* the {@link ExceptionHandlingSpec}
|
||||||
|
* @return the {@link ServerHttpSecurity} to customize
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public ServerHttpSecurity exceptionHandling(Customizer<ExceptionHandlingSpec> exceptionHandlingCustomizer)
|
||||||
|
throws Exception {
|
||||||
|
if (this.exceptionHandling == null) {
|
||||||
|
this.exceptionHandling = new ExceptionHandlingSpec();
|
||||||
|
}
|
||||||
|
exceptionHandlingCustomizer.customize(this.exceptionHandling);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures authorization. An example configuration can be found below:
|
* Configures authorization. An example configuration can be found below:
|
||||||
*
|
*
|
||||||
@ -1624,6 +2075,51 @@ public class ServerHttpSecurity {
|
|||||||
return this.authorizeExchange;
|
return this.authorizeExchange;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures authorization. An example configuration can be found below:
|
||||||
|
*
|
||||||
|
* <pre class="code">
|
||||||
|
* @Bean
|
||||||
|
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
|
||||||
|
* http
|
||||||
|
* // ...
|
||||||
|
* .authorizeExchange(exchanges ->
|
||||||
|
* exchanges
|
||||||
|
* // any URL that starts with /admin/ requires the role "ROLE_ADMIN"
|
||||||
|
* .pathMatchers("/admin/**").hasRole("ADMIN")
|
||||||
|
* // a POST to /users requires the role "USER_POST"
|
||||||
|
* .pathMatchers(HttpMethod.POST, "/users").hasAuthority("USER_POST")
|
||||||
|
* // a request to /users/{username} requires the current authentication's username
|
||||||
|
* // to be equal to the {username}
|
||||||
|
* .pathMatchers("/users/{username}").access((authentication, context) ->
|
||||||
|
* authentication
|
||||||
|
* .map(Authentication::getName)
|
||||||
|
* .map(username -> username.equals(context.getVariables().get("username")))
|
||||||
|
* .map(AuthorizationDecision::new)
|
||||||
|
* )
|
||||||
|
* // allows providing a custom matching strategy that requires the role "ROLE_CUSTOM"
|
||||||
|
* .matchers(customMatcher).hasRole("CUSTOM")
|
||||||
|
* // any other request requires the user to be authenticated
|
||||||
|
* .anyExchange().authenticated()
|
||||||
|
* );
|
||||||
|
* return http.build();
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param authorizeExchangeCustomizer the {@link Customizer} to provide more options for
|
||||||
|
* the {@link AuthorizeExchangeSpec}
|
||||||
|
* @return the {@link ServerHttpSecurity} to customize
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public ServerHttpSecurity authorizeExchange(Customizer<AuthorizeExchangeSpec> authorizeExchangeCustomizer)
|
||||||
|
throws Exception {
|
||||||
|
if (this.authorizeExchange == null) {
|
||||||
|
this.authorizeExchange = new AuthorizeExchangeSpec();
|
||||||
|
}
|
||||||
|
authorizeExchangeCustomizer.customize(this.authorizeExchange);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures log out. An example configuration can be found below:
|
* Configures log out. An example configuration can be found below:
|
||||||
*
|
*
|
||||||
@ -1651,6 +2147,40 @@ public class ServerHttpSecurity {
|
|||||||
return this.logout;
|
return this.logout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures log out. An example configuration can be found below:
|
||||||
|
*
|
||||||
|
* <pre class="code">
|
||||||
|
* @Bean
|
||||||
|
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
|
||||||
|
* http
|
||||||
|
* // ...
|
||||||
|
* .logout(logout ->
|
||||||
|
* logout
|
||||||
|
* // configures how log out is done
|
||||||
|
* .logoutHandler(logoutHandler)
|
||||||
|
* // log out will be performed on POST /signout
|
||||||
|
* .logoutUrl("/signout")
|
||||||
|
* // configure what is done on logout success
|
||||||
|
* .logoutSuccessHandler(successHandler)
|
||||||
|
* );
|
||||||
|
* return http.build();
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param logoutCustomizer the {@link Customizer} to provide more options for
|
||||||
|
* the {@link LogoutSpec}
|
||||||
|
* @return the {@link ServerHttpSecurity} to customize
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public ServerHttpSecurity logout(Customizer<LogoutSpec> logoutCustomizer) throws Exception {
|
||||||
|
if (this.logout == null) {
|
||||||
|
this.logout = new LogoutSpec();
|
||||||
|
}
|
||||||
|
logoutCustomizer.customize(this.logout);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures the request cache which is used when a flow is interrupted (i.e. due to requesting credentials) so
|
* Configures the request cache which is used when a flow is interrupted (i.e. due to requesting credentials) so
|
||||||
* that the request can be replayed after authentication. An example configuration can be found below:
|
* that the request can be replayed after authentication. An example configuration can be found below:
|
||||||
@ -1673,6 +2203,34 @@ public class ServerHttpSecurity {
|
|||||||
return this.requestCache;
|
return this.requestCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the request cache which is used when a flow is interrupted (i.e. due to requesting credentials) so
|
||||||
|
* that the request can be replayed after authentication. An example configuration can be found below:
|
||||||
|
*
|
||||||
|
* <pre class="code">
|
||||||
|
* @Bean
|
||||||
|
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
|
||||||
|
* http
|
||||||
|
* // ...
|
||||||
|
* .requestCache(requestCache ->
|
||||||
|
* requestCache
|
||||||
|
* // configures how the request is cached
|
||||||
|
* .requestCache(customRequestCache)
|
||||||
|
* );
|
||||||
|
* return http.build();
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param requestCacheCustomizer the {@link Customizer} to provide more options for
|
||||||
|
* the {@link RequestCacheSpec}
|
||||||
|
* @return the {@link ServerHttpSecurity} to customize
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public ServerHttpSecurity requestCache(Customizer<RequestCacheSpec> requestCacheCustomizer) throws Exception {
|
||||||
|
requestCacheCustomizer.customize(this.requestCache);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configure the default authentication manager.
|
* Configure the default authentication manager.
|
||||||
* @param manager the authentication manager to use
|
* @param manager the authentication manager to use
|
||||||
@ -2549,6 +3107,19 @@ public class ServerHttpSecurity {
|
|||||||
return new CacheSpec();
|
return new CacheSpec();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures cache control headers
|
||||||
|
*
|
||||||
|
* @param cacheCustomizer the {@link Customizer} to provide more options for
|
||||||
|
* the {@link CacheSpec}
|
||||||
|
* @return the {@link HeaderSpec} to customize
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public HeaderSpec cache(Customizer<CacheSpec> cacheCustomizer) throws Exception {
|
||||||
|
cacheCustomizer.customize(new CacheSpec());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures content type response headers
|
* Configures content type response headers
|
||||||
* @return the {@link ContentTypeOptionsSpec} to configure
|
* @return the {@link ContentTypeOptionsSpec} to configure
|
||||||
@ -2557,6 +3128,20 @@ public class ServerHttpSecurity {
|
|||||||
return new ContentTypeOptionsSpec();
|
return new ContentTypeOptionsSpec();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures content type response headers
|
||||||
|
*
|
||||||
|
* @param contentTypeOptionsCustomizer the {@link Customizer} to provide more options for
|
||||||
|
* the {@link ContentTypeOptionsSpec}
|
||||||
|
* @return the {@link HeaderSpec} to customize
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public HeaderSpec contentTypeOptions(Customizer<ContentTypeOptionsSpec> contentTypeOptionsCustomizer)
|
||||||
|
throws Exception {
|
||||||
|
contentTypeOptionsCustomizer.customize(new ContentTypeOptionsSpec());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures frame options response headers
|
* Configures frame options response headers
|
||||||
* @return the {@link FrameOptionsSpec} to configure
|
* @return the {@link FrameOptionsSpec} to configure
|
||||||
@ -2565,6 +3150,19 @@ public class ServerHttpSecurity {
|
|||||||
return new FrameOptionsSpec();
|
return new FrameOptionsSpec();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures frame options response headers
|
||||||
|
*
|
||||||
|
* @param frameOptionsCustomizer the {@link Customizer} to provide more options for
|
||||||
|
* the {@link FrameOptionsSpec}
|
||||||
|
* @return the {@link HeaderSpec} to customize
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public HeaderSpec frameOptions(Customizer<FrameOptionsSpec> frameOptionsCustomizer) throws Exception {
|
||||||
|
frameOptionsCustomizer.customize(new FrameOptionsSpec());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures the Strict Transport Security response headers
|
* Configures the Strict Transport Security response headers
|
||||||
* @return the {@link HstsSpec} to configure
|
* @return the {@link HstsSpec} to configure
|
||||||
@ -2573,6 +3171,19 @@ public class ServerHttpSecurity {
|
|||||||
return new HstsSpec();
|
return new HstsSpec();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the Strict Transport Security response headers
|
||||||
|
*
|
||||||
|
* @param hstsCustomizer the {@link Customizer} to provide more options for
|
||||||
|
* the {@link HstsSpec}
|
||||||
|
* @return the {@link HeaderSpec} to customize
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public HeaderSpec hsts(Customizer<HstsSpec> hstsCustomizer) throws Exception {
|
||||||
|
hstsCustomizer.customize(new HstsSpec());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
protected void configure(ServerHttpSecurity http) {
|
protected void configure(ServerHttpSecurity http) {
|
||||||
ServerHttpHeadersWriter writer = new CompositeServerHttpHeadersWriter(this.writers);
|
ServerHttpHeadersWriter writer = new CompositeServerHttpHeadersWriter(this.writers);
|
||||||
HttpHeaderWriterWebFilter result = new HttpHeaderWriterWebFilter(writer);
|
HttpHeaderWriterWebFilter result = new HttpHeaderWriterWebFilter(writer);
|
||||||
@ -2587,6 +3198,19 @@ public class ServerHttpSecurity {
|
|||||||
return new XssProtectionSpec();
|
return new XssProtectionSpec();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures x-xss-protection response header.
|
||||||
|
*
|
||||||
|
* @param xssProtectionCustomizer the {@link Customizer} to provide more options for
|
||||||
|
* the {@link XssProtectionSpec}
|
||||||
|
* @return the {@link HeaderSpec} to customize
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public HeaderSpec xssProtection(Customizer<XssProtectionSpec> xssProtectionCustomizer) throws Exception {
|
||||||
|
xssProtectionCustomizer.customize(new XssProtectionSpec());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures {@code Content-Security-Policy} response header.
|
* Configures {@code Content-Security-Policy} response header.
|
||||||
* @param policyDirectives the policy directive(s)
|
* @param policyDirectives the policy directive(s)
|
||||||
@ -2596,6 +3220,20 @@ public class ServerHttpSecurity {
|
|||||||
return new ContentSecurityPolicySpec(policyDirectives);
|
return new ContentSecurityPolicySpec(policyDirectives);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures {@code Content-Security-Policy} response header.
|
||||||
|
*
|
||||||
|
* @param contentSecurityPolicyCustomizer the {@link Customizer} to provide more options for
|
||||||
|
* the {@link ContentSecurityPolicySpec}
|
||||||
|
* @return the {@link HeaderSpec} to customize
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public HeaderSpec contentSecurityPolicy(Customizer<ContentSecurityPolicySpec> contentSecurityPolicyCustomizer)
|
||||||
|
throws Exception {
|
||||||
|
contentSecurityPolicyCustomizer.customize(new ContentSecurityPolicySpec());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures {@code Feature-Policy} response header.
|
* Configures {@code Feature-Policy} response header.
|
||||||
* @param policyDirectives the policy directive(s)
|
* @param policyDirectives the policy directive(s)
|
||||||
@ -2622,6 +3260,20 @@ public class ServerHttpSecurity {
|
|||||||
return new ReferrerPolicySpec();
|
return new ReferrerPolicySpec();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures {@code Referrer-Policy} response header.
|
||||||
|
*
|
||||||
|
* @param referrerPolicyCustomizer the {@link Customizer} to provide more options for
|
||||||
|
* the {@link ReferrerPolicySpec}
|
||||||
|
* @return the {@link HeaderSpec} to customize
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public HeaderSpec referrerPolicy(Customizer<ReferrerPolicySpec> referrerPolicyCustomizer)
|
||||||
|
throws Exception {
|
||||||
|
referrerPolicyCustomizer.customize(new ReferrerPolicySpec());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures cache control headers
|
* Configures cache control headers
|
||||||
* @see #cache()
|
* @see #cache()
|
||||||
@ -2781,6 +3433,7 @@ public class ServerHttpSecurity {
|
|||||||
* @since 5.1
|
* @since 5.1
|
||||||
*/
|
*/
|
||||||
public class ContentSecurityPolicySpec {
|
public class ContentSecurityPolicySpec {
|
||||||
|
private static final String DEFAULT_SRC_SELF_POLICY = "default-src 'self'";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to include the {@code Content-Security-Policy-Report-Only} header in
|
* Whether to include the {@code Content-Security-Policy-Report-Only} header in
|
||||||
@ -2793,6 +3446,17 @@ public class ServerHttpSecurity {
|
|||||||
return HeaderSpec.this;
|
return HeaderSpec.this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the security policy directive(s) to be used in the response header.
|
||||||
|
*
|
||||||
|
* @param policyDirectives the security policy directive(s)
|
||||||
|
* @return the {@link HeaderSpec} to continue configuring
|
||||||
|
*/
|
||||||
|
public HeaderSpec policyDirectives(String policyDirectives) {
|
||||||
|
HeaderSpec.this.contentSecurityPolicy.setPolicyDirectives(policyDirectives);
|
||||||
|
return HeaderSpec.this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows method chaining to continue configuring the
|
* Allows method chaining to continue configuring the
|
||||||
* {@link ServerHttpSecurity}.
|
* {@link ServerHttpSecurity}.
|
||||||
@ -2806,6 +3470,9 @@ public class ServerHttpSecurity {
|
|||||||
HeaderSpec.this.contentSecurityPolicy.setPolicyDirectives(policyDirectives);
|
HeaderSpec.this.contentSecurityPolicy.setPolicyDirectives(policyDirectives);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ContentSecurityPolicySpec() {
|
||||||
|
HeaderSpec.this.contentSecurityPolicy.setPolicyDirectives(DEFAULT_SRC_SELF_POLICY);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2840,6 +3507,17 @@ public class ServerHttpSecurity {
|
|||||||
*/
|
*/
|
||||||
public class ReferrerPolicySpec {
|
public class ReferrerPolicySpec {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the policy to be used in the response header.
|
||||||
|
*
|
||||||
|
* @param referrerPolicy a referrer policy
|
||||||
|
* @return the {@link ReferrerPolicySpec} to continue configuring
|
||||||
|
*/
|
||||||
|
public ReferrerPolicySpec policy(ReferrerPolicy referrerPolicy) {
|
||||||
|
HeaderSpec.this.referrerPolicy.setPolicy(referrerPolicy);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows method chaining to continue configuring the
|
* Allows method chaining to continue configuring the
|
||||||
* {@link ServerHttpSecurity}.
|
* {@link ServerHttpSecurity}.
|
||||||
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -92,6 +92,39 @@ public class AuthorizeExchangeSpecTests {
|
|||||||
.expectStatus().isUnauthorized();
|
.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)
|
@Test(expected = IllegalStateException.class)
|
||||||
public void antMatchersWhenNoAccessAndAnotherMatcherThenThrowsException() {
|
public void antMatchersWhenNoAccessAndAnotherMatcherThenThrowsException() {
|
||||||
this.http
|
this.http
|
||||||
@ -117,6 +150,16 @@ public class AuthorizeExchangeSpecTests {
|
|||||||
this.http.build();
|
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() {
|
private WebTestClient buildClient() {
|
||||||
return WebTestClientBuilder.bindToWebFilters(this.http.build()).build();
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -74,6 +74,14 @@ public class CorsSpecTests {
|
|||||||
assertHeaders();
|
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
|
@Test
|
||||||
public void corsWhenCorsConfigurationSourceBeanThenAccessControlAllowOriginAndSecurityHeaders() {
|
public void corsWhenCorsConfigurationSourceBeanThenAccessControlAllowOriginAndSecurityHeaders() {
|
||||||
when(this.context.getBeanNamesForType(any(ResolvableType.class))).thenReturn(new String[] {"source"}, new String[0]);
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.security.web.server.authorization.ServerAccessDeniedHandler;
|
||||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||||
|
|
||||||
|
import static org.springframework.security.config.Customizer.withDefaults;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Denys Ivano
|
* @author Denys Ivano
|
||||||
* @since 5.0.5
|
* @since 5.0.5
|
||||||
@ -56,6 +58,29 @@ public class ExceptionHandlingSpecTests {
|
|||||||
.expectHeader().valueMatches("WWW-Authenticate", "Basic.*");
|
.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
|
@Test
|
||||||
public void customAuthenticationEntryPoint() {
|
public void customAuthenticationEntryPoint() {
|
||||||
SecurityWebFilterChain securityWebFilter = this.http
|
SecurityWebFilterChain securityWebFilter = this.http
|
||||||
@ -80,6 +105,31 @@ public class ExceptionHandlingSpecTests {
|
|||||||
.expectHeader().valueMatches("Location", ".*");
|
.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
|
@Test
|
||||||
public void defaultAccessDeniedHandler() {
|
public void defaultAccessDeniedHandler() {
|
||||||
SecurityWebFilterChain securityWebFilter = this.http
|
SecurityWebFilterChain securityWebFilter = this.http
|
||||||
@ -104,6 +154,30 @@ public class ExceptionHandlingSpecTests {
|
|||||||
.expectStatus().isForbidden();
|
.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
|
@Test
|
||||||
public void customAccessDeniedHandler() {
|
public void customAccessDeniedHandler() {
|
||||||
SecurityWebFilterChain securityWebFilter = this.http
|
SecurityWebFilterChain securityWebFilter = this.http
|
||||||
@ -129,6 +203,33 @@ public class ExceptionHandlingSpecTests {
|
|||||||
.expectStatus().isBadRequest();
|
.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) {
|
private ServerAuthenticationEntryPoint redirectServerAuthenticationEntryPoint(String location) {
|
||||||
return new RedirectServerAuthenticationEntryPoint(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.BDDMockito.given;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||||
|
import static org.springframework.security.config.Customizer.withDefaults;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Rob Winch
|
* @author Rob Winch
|
||||||
@ -96,6 +97,49 @@ public class FormLoginTests {
|
|||||||
.assertLogout();
|
.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
|
@Test
|
||||||
public void customLoginPage() {
|
public void customLoginPage() {
|
||||||
SecurityWebFilterChain securityWebFilter = this.http
|
SecurityWebFilterChain securityWebFilter = this.http
|
||||||
@ -128,6 +172,40 @@ public class FormLoginTests {
|
|||||||
homePage.assertAt();
|
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
|
@Test
|
||||||
public void authenticationSuccess() {
|
public void authenticationSuccess() {
|
||||||
SecurityWebFilterChain securityWebFilter = this.http
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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 org.springframework.test.web.reactive.server.WebTestClient;
|
||||||
|
|
||||||
import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
|
import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
|
||||||
|
import static org.springframework.security.config.Customizer.withDefaults;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link ServerHttpSecurity.HeaderSpec}.
|
* Tests for {@link ServerHttpSecurity.HeaderSpec}.
|
||||||
@ -49,7 +50,7 @@ import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
|
|||||||
*/
|
*/
|
||||||
public class HeaderSpecTests {
|
public class HeaderSpecTests {
|
||||||
|
|
||||||
private ServerHttpSecurity.HeaderSpec headers = ServerHttpSecurity.http().headers();
|
private ServerHttpSecurity http = ServerHttpSecurity.http();
|
||||||
|
|
||||||
private HttpHeaders expectedHeaders = new HttpHeaders();
|
private HttpHeaders expectedHeaders = new HttpHeaders();
|
||||||
|
|
||||||
@ -72,14 +73,23 @@ public class HeaderSpecTests {
|
|||||||
public void headersWhenDisableThenNoSecurityHeaders() {
|
public void headersWhenDisableThenNoSecurityHeaders() {
|
||||||
new HashSet<>(this.expectedHeaders.keySet()).forEach(this::expectHeaderNamesNotPresent);
|
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();
|
assertHeaders();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void headersWhenDisableAndInvokedExplicitlyThenDefautsUsed() {
|
public void headersWhenDisableAndInvokedExplicitlyThenDefautsUsed() {
|
||||||
this.headers.disable()
|
this.http.headers().disable()
|
||||||
.headers();
|
.headers();
|
||||||
|
|
||||||
assertHeaders();
|
assertHeaders();
|
||||||
@ -87,13 +97,34 @@ public class HeaderSpecTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void headersWhenDefaultsThenAllDefaultsWritten() {
|
public void headersWhenDefaultsThenAllDefaultsWritten() {
|
||||||
|
this.http.headers();
|
||||||
|
|
||||||
|
assertHeaders();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void headersWhenDefaultsInLambdaThenAllDefaultsWritten() throws Exception {
|
||||||
|
this.http.headers(withDefaults());
|
||||||
|
|
||||||
assertHeaders();
|
assertHeaders();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void headersWhenCacheDisableThenCacheNotWritten() {
|
public void headersWhenCacheDisableThenCacheNotWritten() {
|
||||||
expectHeaderNamesNotPresent(HttpHeaders.CACHE_CONTROL, HttpHeaders.PRAGMA, HttpHeaders.EXPIRES);
|
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();
|
assertHeaders();
|
||||||
}
|
}
|
||||||
@ -101,7 +132,18 @@ public class HeaderSpecTests {
|
|||||||
@Test
|
@Test
|
||||||
public void headersWhenContentOptionsDisableThenContentTypeOptionsNotWritten() {
|
public void headersWhenContentOptionsDisableThenContentTypeOptionsNotWritten() {
|
||||||
expectHeaderNamesNotPresent(ContentTypeOptionsServerHttpHeadersWriter.X_CONTENT_OPTIONS);
|
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();
|
assertHeaders();
|
||||||
}
|
}
|
||||||
@ -109,7 +151,18 @@ public class HeaderSpecTests {
|
|||||||
@Test
|
@Test
|
||||||
public void headersWhenHstsDisableThenHstsNotWritten() {
|
public void headersWhenHstsDisableThenHstsNotWritten() {
|
||||||
expectHeaderNamesNotPresent(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY);
|
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();
|
assertHeaders();
|
||||||
}
|
}
|
||||||
@ -118,28 +171,73 @@ public class HeaderSpecTests {
|
|||||||
public void headersWhenHstsCustomThenCustomHstsWritten() {
|
public void headersWhenHstsCustomThenCustomHstsWritten() {
|
||||||
this.expectedHeaders.remove(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY);
|
this.expectedHeaders.remove(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY);
|
||||||
this.expectedHeaders.add(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, "max-age=60");
|
this.expectedHeaders.add(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, "max-age=60");
|
||||||
this.headers.hsts()
|
this.http.headers().hsts()
|
||||||
.maxAge(Duration.ofSeconds(60))
|
.maxAge(Duration.ofSeconds(60))
|
||||||
.includeSubdomains(false);
|
.includeSubdomains(false);
|
||||||
|
|
||||||
assertHeaders();
|
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
|
@Test
|
||||||
public void headersWhenHstsCustomWithPreloadThenCustomHstsWritten() {
|
public void headersWhenHstsCustomWithPreloadThenCustomHstsWritten() {
|
||||||
this.expectedHeaders.remove(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY);
|
this.expectedHeaders.remove(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY);
|
||||||
this.expectedHeaders.add(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, "max-age=60 ; includeSubDomains ; preload");
|
this.expectedHeaders.add(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, "max-age=60 ; includeSubDomains ; preload");
|
||||||
this.headers.hsts()
|
this.http.headers().hsts()
|
||||||
.maxAge(Duration.ofSeconds(60))
|
.maxAge(Duration.ofSeconds(60))
|
||||||
.preload(true);
|
.preload(true);
|
||||||
|
|
||||||
assertHeaders();
|
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
|
@Test
|
||||||
public void headersWhenFrameOptionsDisableThenFrameOptionsNotWritten() {
|
public void headersWhenFrameOptionsDisableThenFrameOptionsNotWritten() {
|
||||||
expectHeaderNamesNotPresent(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS);
|
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();
|
assertHeaders();
|
||||||
}
|
}
|
||||||
@ -147,17 +245,43 @@ public class HeaderSpecTests {
|
|||||||
@Test
|
@Test
|
||||||
public void headersWhenFrameOptionsModeThenFrameOptionsCustomMode() {
|
public void headersWhenFrameOptionsModeThenFrameOptionsCustomMode() {
|
||||||
this.expectedHeaders.set(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS, "SAMEORIGIN");
|
this.expectedHeaders.set(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS, "SAMEORIGIN");
|
||||||
this.headers
|
this.http.headers()
|
||||||
.frameOptions()
|
.frameOptions()
|
||||||
.mode(XFrameOptionsServerHttpHeadersWriter.Mode.SAMEORIGIN);
|
.mode(XFrameOptionsServerHttpHeadersWriter.Mode.SAMEORIGIN);
|
||||||
|
|
||||||
assertHeaders();
|
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
|
@Test
|
||||||
public void headersWhenXssProtectionDisableThenXssProtectionNotWritten() {
|
public void headersWhenXssProtectionDisableThenXssProtectionNotWritten() {
|
||||||
expectHeaderNamesNotPresent("X-Xss-Protection");
|
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();
|
assertHeaders();
|
||||||
}
|
}
|
||||||
@ -168,7 +292,7 @@ public class HeaderSpecTests {
|
|||||||
this.expectedHeaders.add(FeaturePolicyServerHttpHeadersWriter.FEATURE_POLICY,
|
this.expectedHeaders.add(FeaturePolicyServerHttpHeadersWriter.FEATURE_POLICY,
|
||||||
policyDirectives);
|
policyDirectives);
|
||||||
|
|
||||||
this.headers.featurePolicy(policyDirectives);
|
this.http.headers().featurePolicy(policyDirectives);
|
||||||
|
|
||||||
assertHeaders();
|
assertHeaders();
|
||||||
}
|
}
|
||||||
@ -179,7 +303,39 @@ public class HeaderSpecTests {
|
|||||||
this.expectedHeaders.add(ContentSecurityPolicyServerHttpHeadersWriter.CONTENT_SECURITY_POLICY,
|
this.expectedHeaders.add(ContentSecurityPolicyServerHttpHeadersWriter.CONTENT_SECURITY_POLICY,
|
||||||
policyDirectives);
|
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();
|
assertHeaders();
|
||||||
}
|
}
|
||||||
@ -188,7 +344,20 @@ public class HeaderSpecTests {
|
|||||||
public void headersWhenReferrerPolicyEnabledThenFeaturePolicyWritten() {
|
public void headersWhenReferrerPolicyEnabledThenFeaturePolicyWritten() {
|
||||||
this.expectedHeaders.add(ReferrerPolicyServerHttpHeadersWriter.REFERRER_POLICY,
|
this.expectedHeaders.add(ReferrerPolicyServerHttpHeadersWriter.REFERRER_POLICY,
|
||||||
ReferrerPolicy.NO_REFERRER.getPolicy());
|
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();
|
assertHeaders();
|
||||||
}
|
}
|
||||||
@ -197,7 +366,23 @@ public class HeaderSpecTests {
|
|||||||
public void headersWhenReferrerPolicyCustomEnabledThenFeaturePolicyCustomWritten() {
|
public void headersWhenReferrerPolicyCustomEnabledThenFeaturePolicyCustomWritten() {
|
||||||
this.expectedHeaders.add(ReferrerPolicyServerHttpHeadersWriter.REFERRER_POLICY,
|
this.expectedHeaders.add(ReferrerPolicyServerHttpHeadersWriter.REFERRER_POLICY,
|
||||||
ReferrerPolicy.NO_REFERRER_WHEN_DOWNGRADE.getPolicy());
|
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();
|
assertHeaders();
|
||||||
}
|
}
|
||||||
@ -228,6 +413,6 @@ public class HeaderSpecTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private WebTestClient buildClient() {
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.mock;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
import static org.springframework.security.config.Customizer.withDefaults;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link HttpsRedirectSpecTests}
|
* Tests for {@link HttpsRedirectSpecTests}
|
||||||
@ -71,6 +72,17 @@ public class HttpsRedirectSpecTests {
|
|||||||
.expectHeader().valueEquals(HttpHeaders.LOCATION, "https://localhost");
|
.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
|
@Test
|
||||||
public void getWhenInsecureAndPathRequiresTransportSecurityThenRedirects() {
|
public void getWhenInsecureAndPathRequiresTransportSecurityThenRedirects() {
|
||||||
this.spring.register(SometimesRedirectToHttpsConfig.class).autowire();
|
this.spring.register(SometimesRedirectToHttpsConfig.class).autowire();
|
||||||
@ -87,6 +99,22 @@ public class HttpsRedirectSpecTests {
|
|||||||
.expectHeader().valueEquals(HttpHeaders.LOCATION, "https://localhost:8443/secure");
|
.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
|
@Test
|
||||||
public void getWhenInsecureAndUsingCustomPortMapperThenRespondsWithRedirectToSecurePort() {
|
public void getWhenInsecureAndUsingCustomPortMapperThenRespondsWithRedirectToSecurePort() {
|
||||||
this.spring.register(RedirectToHttpsViaCustomPortsConfig.class).autowire();
|
this.spring.register(RedirectToHttpsViaCustomPortsConfig.class).autowire();
|
||||||
@ -101,6 +129,20 @@ public class HttpsRedirectSpecTests {
|
|||||||
.expectHeader().valueEquals(HttpHeaders.LOCATION, "https://localhost:4443");
|
.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
|
@EnableWebFlux
|
||||||
@EnableWebFluxSecurity
|
@EnableWebFluxSecurity
|
||||||
static class RedirectToHttpConfig {
|
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
|
@EnableWebFlux
|
||||||
@EnableWebFluxSecurity
|
@EnableWebFluxSecurity
|
||||||
static class SometimesRedirectToHttpsConfig {
|
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
|
@EnableWebFlux
|
||||||
@EnableWebFluxSecurity
|
@EnableWebFluxSecurity
|
||||||
static class RedirectToHttpsViaCustomPortsConfig {
|
static class RedirectToHttpsViaCustomPortsConfig {
|
||||||
@ -149,4 +224,26 @@ public class HttpsRedirectSpecTests {
|
|||||||
return mock(PortMapper.class);
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.test.web.reactive.server.WebTestClient;
|
||||||
import org.springframework.security.test.web.reactive.server.WebTestClientBuilder;
|
import org.springframework.security.test.web.reactive.server.WebTestClientBuilder;
|
||||||
|
|
||||||
|
import static org.springframework.security.config.Customizer.withDefaults;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Shazin Sadakath
|
* @author Shazin Sadakath
|
||||||
* @since 5.0
|
* @since 5.0
|
||||||
@ -117,4 +119,49 @@ public class LogoutSpecTests {
|
|||||||
.assertAt()
|
.assertAt()
|
||||||
.assertLogout();
|
.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();
|
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
|
@Test
|
||||||
public void oauth2LoginWhenCustomBeansThenUsed() {
|
public void oauth2LoginWhenCustomBeansThenUsed() {
|
||||||
this.spring.register(OAuth2LoginWithMultipleClientRegistrations.class,
|
this.spring.register(OAuth2LoginWithMultipleClientRegistrations.class,
|
||||||
|
@ -175,6 +175,27 @@ public class OAuth2ResourceServerSpecTests {
|
|||||||
.expectHeader().value(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer error=\"invalid_token\""));
|
.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
|
@Test
|
||||||
public void getWhenValidUsingPlaceholderThenReturnsOk() {
|
public void getWhenValidUsingPlaceholderThenReturnsOk() {
|
||||||
this.spring.register(PlaceholderConfig.class, RootController.class).autowire();
|
this.spring.register(PlaceholderConfig.class, RootController.class).autowire();
|
||||||
@ -213,6 +234,18 @@ public class OAuth2ResourceServerSpecTests {
|
|||||||
.expectStatus().isOk();
|
.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
|
@Test
|
||||||
public void getWhenUsingCustomAuthenticationManagerThenUsesItAccordingly() {
|
public void getWhenUsingCustomAuthenticationManagerThenUsesItAccordingly() {
|
||||||
@ -230,6 +263,22 @@ public class OAuth2ResourceServerSpecTests {
|
|||||||
.expectHeader().value(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer error=\"mock-failure\""));
|
.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
|
@Test
|
||||||
public void getWhenUsingCustomAuthenticationManagerResolverThenUsesItAccordingly() {
|
public void getWhenUsingCustomAuthenticationManagerResolverThenUsesItAccordingly() {
|
||||||
this.spring.register(CustomAuthenticationManagerResolverConfig.class).autowire();
|
this.spring.register(CustomAuthenticationManagerResolverConfig.class).autowire();
|
||||||
@ -396,6 +445,18 @@ public class OAuth2ResourceServerSpecTests {
|
|||||||
.expectStatus().isOk();
|
.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
|
@EnableWebFlux
|
||||||
@EnableWebFluxSecurity
|
@EnableWebFluxSecurity
|
||||||
static class PublicKeyConfig {
|
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
|
@EnableWebFlux
|
||||||
@EnableWebFluxSecurity
|
@EnableWebFluxSecurity
|
||||||
static class PlaceholderConfig {
|
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
|
@EnableWebFlux
|
||||||
@EnableWebFluxSecurity
|
@EnableWebFluxSecurity
|
||||||
static class CustomDecoderConfig {
|
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
|
@EnableWebFlux
|
||||||
@EnableWebFluxSecurity
|
@EnableWebFluxSecurity
|
||||||
static class CustomAuthenticationManagerResolverConfig {
|
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
|
@RestController
|
||||||
static class RootController {
|
static class RootController {
|
||||||
@GetMapping
|
@GetMapping
|
||||||
|
@ -34,6 +34,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
|
|||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.springframework.security.config.Customizer.withDefaults;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Rob Winch
|
* @author Rob Winch
|
||||||
@ -103,6 +104,40 @@ public class RequestCacheTests {
|
|||||||
securedPage.assertAt();
|
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 {
|
public static class SecuredPage {
|
||||||
private WebDriver driver;
|
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.BDDMockito.given;
|
||||||
import static org.mockito.Matchers.any;
|
import static org.mockito.Matchers.any;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
import static org.springframework.security.config.Customizer.withDefaults;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -36,6 +38,7 @@ import org.junit.runner.RunWith;
|
|||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.MockitoJUnitRunner;
|
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.authentication.preauth.x509.X509PrincipalExtractor;
|
||||||
import org.springframework.security.web.server.authentication.ServerX509AuthenticationConverter;
|
import org.springframework.security.web.server.authentication.ServerX509AuthenticationConverter;
|
||||||
import reactor.core.publisher.Mono;
|
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
|
@Test
|
||||||
public void basicWithAnonymous() {
|
public void basicWithAnonymous() {
|
||||||
given(this.authenticationManager.authenticate(any())).willReturn(Mono.just(new TestingAuthenticationToken("rob", "rob", "ROLE_USER", "ROLE_ADMIN")));
|
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();
|
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
|
@Test
|
||||||
public void basicWithCustomAuthenticationManager() {
|
public void basicWithCustomAuthenticationManager() {
|
||||||
ReactiveAuthenticationManager customAuthenticationManager = mock(ReactiveAuthenticationManager.class);
|
ReactiveAuthenticationManager customAuthenticationManager = mock(ReactiveAuthenticationManager.class);
|
||||||
@ -302,6 +343,31 @@ public class ServerHttpSecurityTests {
|
|||||||
verifyZeroInteractions(this.authenticationManager);
|
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
|
@Test
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void addsX509FilterWhenX509AuthenticationIsConfigured() {
|
public void addsX509FilterWhenX509AuthenticationIsConfigured() {
|
||||||
@ -319,6 +385,23 @@ public class ServerHttpSecurityTests {
|
|||||||
assertThat(x509WebFilter).isNotNull();
|
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
|
@Test
|
||||||
public void addsX509FilterWhenX509AuthenticationIsConfiguredWithDefaults() {
|
public void addsX509FilterWhenX509AuthenticationIsConfiguredWithDefaults() {
|
||||||
this.http.x509();
|
this.http.x509();
|
||||||
@ -329,6 +412,46 @@ public class ServerHttpSecurityTests {
|
|||||||
assertThat(x509WebFilter).isNotNull();
|
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) {
|
private boolean isX509Filter(WebFilter filter) {
|
||||||
try {
|
try {
|
||||||
Object converter = ReflectionTestUtils.getField(filter, "authenticationConverter");
|
Object converter = ReflectionTestUtils.getField(filter, "authenticationConverter");
|
||||||
|
@ -28,10 +28,10 @@ The following will disable the CORS integration within Spring Security:
|
|||||||
[source,java]
|
[source,java]
|
||||||
----
|
----
|
||||||
@Bean
|
@Bean
|
||||||
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
// ...
|
// ...
|
||||||
.cors().disable();
|
.cors(cors -> cors.disable());
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
@ -53,12 +53,20 @@ You can easily do this with the following Java Configuration:
|
|||||||
[source,java]
|
[source,java]
|
||||||
----
|
----
|
||||||
@Bean
|
@Bean
|
||||||
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
// ...
|
// ...
|
||||||
.headers()
|
.headers(headers ->
|
||||||
.hsts().disable()
|
headers
|
||||||
.frameOptions().mode(Mode.SAMEORIGIN);
|
.hsts(hsts ->
|
||||||
|
hsts
|
||||||
|
.disable()
|
||||||
|
)
|
||||||
|
.frameOptions(frameOptions ->
|
||||||
|
frameOptions
|
||||||
|
.mode(Mode.SAMEORIGIN)
|
||||||
|
)
|
||||||
|
);
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
@ -72,11 +80,13 @@ If necessary, you can disable all of the HTTP Security response headers with the
|
|||||||
[source,java]
|
[source,java]
|
||||||
----
|
----
|
||||||
@Bean
|
@Bean
|
||||||
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
// ...
|
// ...
|
||||||
.headers()
|
.headers(headers ->
|
||||||
.disable();
|
headers
|
||||||
|
.disable()
|
||||||
|
);
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
@ -104,11 +114,13 @@ You can also disable cache control using the following Java Configuration:
|
|||||||
[source,java]
|
[source,java]
|
||||||
----
|
----
|
||||||
@Bean
|
@Bean
|
||||||
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
// ...
|
// ...
|
||||||
.headers()
|
.headers(headers ->
|
||||||
.cache().disable();
|
headers
|
||||||
|
.cache(cache -> cache.disable())
|
||||||
|
);
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
@ -143,11 +155,13 @@ However, if need to disable the header, the following may be used:
|
|||||||
[source,java]
|
[source,java]
|
||||||
----
|
----
|
||||||
@Bean
|
@Bean
|
||||||
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
// ...
|
// ...
|
||||||
.headers()
|
.headers(headers ->
|
||||||
.contentTypeOptions().disable();
|
headers
|
||||||
|
.contentTypeOptions(contentTypeOptions -> contentTypeOptions.disable())
|
||||||
|
);
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
@ -188,14 +202,18 @@ You can customize HSTS headers with Java Configuration:
|
|||||||
[source,java]
|
[source,java]
|
||||||
----
|
----
|
||||||
@Bean
|
@Bean
|
||||||
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
// ...
|
// ...
|
||||||
.headers()
|
.headers(headers ->
|
||||||
.hsts()
|
headers
|
||||||
.includeSubdomains(true)
|
.hsts(hsts ->
|
||||||
.preload(true)
|
hsts
|
||||||
.maxAge(Duration.ofDays(365));
|
.includeSubdomains(true)
|
||||||
|
.preload(true)
|
||||||
|
.maxAge(Duration.ofDays(365))
|
||||||
|
)
|
||||||
|
);
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
@ -232,12 +250,16 @@ You can customize X-Frame-Options with Java Configuration using the following:
|
|||||||
[source,java]
|
[source,java]
|
||||||
----
|
----
|
||||||
@Bean
|
@Bean
|
||||||
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
// ...
|
// ...
|
||||||
.headers()
|
.headers(headers ->
|
||||||
.frameOptions()
|
headers
|
||||||
.mode(SAMEORIGIN);
|
.frameOptions(frameOptions ->
|
||||||
|
frameOptions
|
||||||
|
.mode(SAMEORIGIN)
|
||||||
|
)
|
||||||
|
);
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
@ -264,12 +286,13 @@ However, we can customize with Java Configuration with the following:
|
|||||||
[source,java]
|
[source,java]
|
||||||
----
|
----
|
||||||
@Bean
|
@Bean
|
||||||
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
// ...
|
// ...
|
||||||
.headers()
|
.headers(headers ->
|
||||||
.xssProtection()
|
headers
|
||||||
.disable();
|
.xssProtection(xssProtection -> xssProtection.disable())
|
||||||
|
);
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
@ -345,11 +368,16 @@ You can enable the CSP header using Java configuration as shown below:
|
|||||||
[source,java]
|
[source,java]
|
||||||
----
|
----
|
||||||
@Bean
|
@Bean
|
||||||
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
// ...
|
// ...
|
||||||
.headers()
|
.headers(headers ->
|
||||||
.contentSecurityPolicy("script-src 'self' https://trustedscripts.example.com; object-src https://trustedplugins.example.com; report-uri /csp-report-endpoint/");
|
headers
|
||||||
|
.contentSecurityPolicy(contentSecurityPolicy ->
|
||||||
|
contentSecurityPolicy
|
||||||
|
.policyDirectives("script-src 'self' https://trustedscripts.example.com; object-src https://trustedplugins.example.com; report-uri /csp-report-endpoint/")
|
||||||
|
)
|
||||||
|
);
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
@ -359,12 +387,17 @@ To enable the CSP _'report-only'_ header, provide the following Java configurati
|
|||||||
[source,java]
|
[source,java]
|
||||||
----
|
----
|
||||||
@Bean
|
@Bean
|
||||||
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
// ...
|
// ...
|
||||||
.headers()
|
.headers(headers ->
|
||||||
.contentSecurityPolicy("script-src 'self' https://trustedscripts.example.com; object-src https://trustedplugins.example.com; report-uri /csp-report-endpoint/")
|
headers
|
||||||
.reportOnly();
|
.contentSecurityPolicy(contentSecurityPolicy ->
|
||||||
|
contentSecurityPolicy
|
||||||
|
.policyDirectives("script-src 'self' https://trustedscripts.example.com; object-src https://trustedplugins.example.com; report-uri /csp-report-endpoint/")
|
||||||
|
.reportOnly()
|
||||||
|
)
|
||||||
|
);
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
@ -405,11 +438,16 @@ You can enable the Referrer-Policy header using Java configuration as shown belo
|
|||||||
[source,java]
|
[source,java]
|
||||||
----
|
----
|
||||||
@Bean
|
@Bean
|
||||||
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
// ...
|
// ...
|
||||||
.headers()
|
.headers(headers ->
|
||||||
.referrerPolicy(ReferrerPolicy.SAME_ORIGIN);
|
headers
|
||||||
|
.referrerPolicy(referrerPolicy ->
|
||||||
|
referrerPolicy
|
||||||
|
.policy(ReferrerPolicy.SAME_ORIGIN)
|
||||||
|
)
|
||||||
|
);
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
@ -438,11 +476,13 @@ You can enable the Feature-Policy header using Java configuration as shown below
|
|||||||
[source,java]
|
[source,java]
|
||||||
----
|
----
|
||||||
@Bean
|
@Bean
|
||||||
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
// ...
|
// ...
|
||||||
.headers()
|
.headers(headers ->
|
||||||
.featurePolicy("geolocation 'self'");
|
headers
|
||||||
|
.featurePolicy("geolocation 'self'")
|
||||||
|
);
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
@ -88,10 +88,11 @@ public class SecurityConfig {
|
|||||||
return http
|
return http
|
||||||
// Demonstrate that method security works
|
// Demonstrate that method security works
|
||||||
// Best practice to use both for defense in depth
|
// Best practice to use both for defense in depth
|
||||||
.authorizeExchange()
|
.authorizeExchange(exchanges ->
|
||||||
.anyExchange().permitAll()
|
exchanges
|
||||||
.and()
|
.anyExchange().permitAll()
|
||||||
.httpBasic().and()
|
)
|
||||||
|
.httpBasic(withDefaults())
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ The next step is to instruct Spring Security that you wish to act as an OAuth2 C
|
|||||||
SecurityWebFilterChain configure(ServerHttpSecurity http) throws Exception {
|
SecurityWebFilterChain configure(ServerHttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
// ...
|
// ...
|
||||||
.oauth2Client();
|
.oauth2Client(withDefaults());
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
@ -128,10 +128,10 @@ ReactiveClientRegistrationRepository clientRegistrations() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
// ...
|
// ...
|
||||||
.oauth2Login();
|
.oauth2Login(withDefaults());
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
@ -141,14 +141,16 @@ Additional configuration options can be seen below:
|
|||||||
[source,java]
|
[source,java]
|
||||||
----
|
----
|
||||||
@Bean
|
@Bean
|
||||||
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
// ...
|
// ...
|
||||||
.oauth2Login()
|
.oauth2Login(oauth2Login ->
|
||||||
.authenticationConverter(converter)
|
oauth2Login
|
||||||
.authenticationManager(manager)
|
.authenticationConverter(converter)
|
||||||
.authorizedClientRepository(authorizedClients)
|
.authenticationManager(manager)
|
||||||
.clientRegistrationRepository(clientRegistrations);
|
.authorizedClientRepository(authorizedClients)
|
||||||
|
.clientRegistrationRepository(clientRegistrations)
|
||||||
|
);
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
@ -121,14 +121,17 @@ The first is a `SecurityWebFilterChain` that configures the app as a resource se
|
|||||||
[source,java]
|
[source,java]
|
||||||
----
|
----
|
||||||
@Bean
|
@Bean
|
||||||
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
.authorizeExchange()
|
.authorizeExchange(exchanges ->
|
||||||
.anyExchange().authenticated()
|
exchanges
|
||||||
.and()
|
.anyExchange().authenticated()
|
||||||
.oauth2ResourceServer()
|
)
|
||||||
.jwt();
|
.oauth2ResourceServer(oauth2ResourceServer ->
|
||||||
return http.build();
|
oauth2ResourceServer
|
||||||
|
.jwt(withDefaults())
|
||||||
|
);
|
||||||
|
return http.build();
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
|
||||||
@ -139,14 +142,17 @@ Replacing this is as simple as exposing the bean within the application:
|
|||||||
[source,java]
|
[source,java]
|
||||||
----
|
----
|
||||||
@Bean
|
@Bean
|
||||||
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
.authorizeExchange()
|
.authorizeExchange(exchanges ->
|
||||||
.pathMatchers("/message/**").hasAuthority("SCOPE_message:read")
|
exchanges
|
||||||
.anyExchange().authenticated()
|
.pathMatchers("/message/**").hasAuthority("SCOPE_message:read")
|
||||||
.and()
|
.anyExchange().authenticated()
|
||||||
.oauth2ResourceServer()
|
)
|
||||||
.jwt();
|
.oauth2ResourceServer(oauth2ResourceServer ->
|
||||||
|
oauth2ResourceServer
|
||||||
|
.jwt(withDefaults())
|
||||||
|
);
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
@ -177,15 +183,20 @@ An authorization server's JWK Set Uri can be configured <<webflux-oauth2-resourc
|
|||||||
[source,java]
|
[source,java]
|
||||||
----
|
----
|
||||||
@Bean
|
@Bean
|
||||||
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
.authorizeExchange()
|
.authorizeExchange(exchanges ->
|
||||||
.anyExchange().authenticated()
|
exchanges
|
||||||
.and()
|
.anyExchange().authenticated()
|
||||||
.oauth2ResourceServer()
|
)
|
||||||
.jwt()
|
.oauth2ResourceServer(oauth2ResourceServer ->
|
||||||
.jwkSetUri("https://idp.example.com/.well-known/jwks.json");
|
oauth2ResourceServer
|
||||||
return http.build();
|
.jwt(jwt ->
|
||||||
|
jwt
|
||||||
|
.jwkSetUri("https://idp.example.com/.well-known/jwks.json")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return http.build();
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
|
||||||
@ -199,14 +210,19 @@ More powerful than `jwkSetUri()` is `decoder()`, which will completely replace a
|
|||||||
[source,java]
|
[source,java]
|
||||||
----
|
----
|
||||||
@Bean
|
@Bean
|
||||||
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
.authorizeExchange()
|
.authorizeExchange(exchanges ->
|
||||||
.anyExchange().authenticated()
|
exchanges
|
||||||
.and()
|
.anyExchange().authenticated()
|
||||||
.oauth2ResourceServer()
|
)
|
||||||
.jwt()
|
.oauth2ResourceServer(oauth2ResourceServer ->
|
||||||
.decoder(myCustomDecoder());
|
oauth2ResourceServer
|
||||||
|
.jwt(jwt ->
|
||||||
|
jwt
|
||||||
|
.decoder(myCustomDecoder())
|
||||||
|
)
|
||||||
|
);
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
@ -240,15 +256,18 @@ This means that to protect an endpoint or method with a scope derived from a JWT
|
|||||||
[source,java]
|
[source,java]
|
||||||
----
|
----
|
||||||
@Bean
|
@Bean
|
||||||
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
.authorizeExchange()
|
.authorizeExchange(exchanges ->
|
||||||
.mvcMatchers("/contacts/**").hasAuthority("SCOPE_contacts")
|
exchanges
|
||||||
.mvcMatchers("/messages/**").hasAuthority("SCOPE_messages")
|
.mvcMatchers("/contacts/**").hasAuthority("SCOPE_contacts")
|
||||||
.anyExchange().authenticated()
|
.mvcMatchers("/messages/**").hasAuthority("SCOPE_messages")
|
||||||
.and()
|
.anyExchange().authenticated()
|
||||||
.oauth2ResourceServer()
|
)
|
||||||
.jwt();
|
.oauth2ResourceServer(oauth2ResourceServer ->
|
||||||
|
oauth2ResourceServer
|
||||||
|
.jwt(withDefaults())
|
||||||
|
);
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
@ -273,15 +292,20 @@ To this end, the DSL exposes `jwtAuthenticationConverter()`:
|
|||||||
[source,java]
|
[source,java]
|
||||||
----
|
----
|
||||||
@Bean
|
@Bean
|
||||||
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
.authorizeExchange()
|
.authorizeExchange(exchanges ->
|
||||||
.anyExchange().authenticated()
|
exchanges
|
||||||
.and()
|
.anyExchange().authenticated()
|
||||||
.oauth2ResourceServer()
|
)
|
||||||
.jwt()
|
.oauth2ResourceServer(oauth2ResourceServer ->
|
||||||
.jwtAuthenticationConverter(grantedAuthoritiesExtractor());
|
oauth2ResourceServer
|
||||||
return http.build();
|
.jwt(jwt ->
|
||||||
|
jwt
|
||||||
|
.jwtAuthenticationConverter(grantedAuthoritiesExtractor())
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return http.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
Converter<Jwt, Mono<AbstractAuthenticationToken>> grantedAuthoritiesExtractor() {
|
Converter<Jwt, Mono<AbstractAuthenticationToken>> grantedAuthoritiesExtractor() {
|
||||||
|
@ -7,10 +7,10 @@ Spring Security can be configured to perform a redirect to https using the follo
|
|||||||
[source,java]
|
[source,java]
|
||||||
----
|
----
|
||||||
@Bean
|
@Bean
|
||||||
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
// ...
|
// ...
|
||||||
.redirectToHttps();
|
.redirectToHttps(withDefaults());
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
@ -22,11 +22,13 @@ For example, if the production environment adds a header named `X-Forwarded-Prot
|
|||||||
[source,java]
|
[source,java]
|
||||||
----
|
----
|
||||||
@Bean
|
@Bean
|
||||||
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
// ...
|
// ...
|
||||||
.redirectToHttps()
|
.redirectToHttps(redirectToHttps ->
|
||||||
.httpsRedirectWhen(e -> e.getRequest().getHeaders().containsKey("X-Forwarded-Proto"));
|
redirectToHttps
|
||||||
|
.httpsRedirectWhen(e -> e.getRequest().getHeaders().containsKey("X-Forwarded-Proto"))
|
||||||
|
);
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
@ -52,13 +52,14 @@ public class HelloWebfluxSecurityConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
.authorizeExchange()
|
.authorizeExchange(exchanges ->
|
||||||
.anyExchange().authenticated()
|
exchanges
|
||||||
.and()
|
.anyExchange().authenticated()
|
||||||
.httpBasic().and()
|
)
|
||||||
.formLogin();
|
.httpBasic(withDefaults())
|
||||||
|
.formLogin(withDefaults());
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,14 +7,14 @@ Below is an example of a reactive x509 security configuration:
|
|||||||
[source,java]
|
[source,java]
|
||||||
----
|
----
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
|
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
.x509()
|
.x509(withDefaults())
|
||||||
.and()
|
.authorizeExchange(exchanges ->
|
||||||
.authorizeExchange()
|
exchanges
|
||||||
.anyExchange().permitAll();
|
.anyExchange().permitAll()
|
||||||
|
);
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
|
||||||
@ -25,28 +25,28 @@ The next example demonstrates how these defaults can be overridden.
|
|||||||
[source,java]
|
[source,java]
|
||||||
----
|
----
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
|
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) throws Exception {
|
||||||
SubjectDnX509PrincipalExtractor principalExtractor =
|
SubjectDnX509PrincipalExtractor principalExtractor =
|
||||||
new SubjectDnX509PrincipalExtractor();
|
new SubjectDnX509PrincipalExtractor();
|
||||||
|
|
||||||
principalExtractor.setSubjectDnRegex("OU=(.*?)(?:,|$)");
|
principalExtractor.setSubjectDnRegex("OU=(.*?)(?:,|$)");
|
||||||
|
|
||||||
ReactiveAuthenticationManager authenticationManager = authentication -> {
|
ReactiveAuthenticationManager authenticationManager = authentication -> {
|
||||||
authentication.setAuthenticated("Trusted Org Unit".equals(authentication.getName()));
|
authentication.setAuthenticated("Trusted Org Unit".equals(authentication.getName()));
|
||||||
return Mono.just(authentication);
|
return Mono.just(authentication);
|
||||||
};
|
};
|
||||||
|
|
||||||
// @formatter:off
|
http
|
||||||
http
|
.x509(x509 ->
|
||||||
.x509()
|
x509
|
||||||
.principalExtractor(principalExtractor)
|
.principalExtractor(principalExtractor)
|
||||||
.authenticationManager(authenticationManager)
|
.authenticationManager(authenticationManager)
|
||||||
.and()
|
)
|
||||||
.authorizeExchange()
|
.authorizeExchange(exchanges ->
|
||||||
.anyExchange().authenticated();
|
exchanges
|
||||||
// @formatter:on
|
.anyExchange().authenticated()
|
||||||
|
);
|
||||||
return http.build();
|
return http.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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -25,6 +25,8 @@ import org.springframework.security.core.userdetails.User;
|
|||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||||
|
|
||||||
|
import static org.springframework.security.config.Customizer.withDefaults;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Rob Winch
|
* @author Rob Winch
|
||||||
* @since 5.0
|
* @since 5.0
|
||||||
@ -38,10 +40,11 @@ public class SecurityConfig {
|
|||||||
return http
|
return http
|
||||||
// Demonstrate that method security works
|
// Demonstrate that method security works
|
||||||
// Best practice to use both for defense in depth
|
// Best practice to use both for defense in depth
|
||||||
.authorizeExchange()
|
.authorizeExchange(exchanges ->
|
||||||
.anyExchange().permitAll()
|
exchanges
|
||||||
.and()
|
.anyExchange().permitAll()
|
||||||
.httpBasic().and()
|
)
|
||||||
|
.httpBasic(withDefaults())
|
||||||
.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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -21,6 +21,8 @@ import org.springframework.security.config.annotation.web.reactive.EnableWebFlux
|
|||||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||||
|
|
||||||
|
import static org.springframework.security.config.Customizer.withDefaults;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Rob Winch
|
* @author Rob Winch
|
||||||
* @since 5.1
|
* @since 5.1
|
||||||
@ -31,12 +33,15 @@ public class SecurityConfig {
|
|||||||
@Bean
|
@Bean
|
||||||
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
|
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
.authorizeExchange()
|
.authorizeExchange(exchanges ->
|
||||||
.pathMatchers("/message/**").hasAuthority("SCOPE_message:read")
|
exchanges
|
||||||
.anyExchange().authenticated()
|
.pathMatchers("/message/**").hasAuthority("SCOPE_message:read")
|
||||||
.and()
|
.anyExchange().authenticated()
|
||||||
.oauth2ResourceServer()
|
)
|
||||||
.jwt();
|
.oauth2ResourceServer(oauth2ResourceServer ->
|
||||||
|
oauth2ResourceServer
|
||||||
|
.jwt(withDefaults())
|
||||||
|
);
|
||||||
return http.build();
|
return http.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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -23,6 +23,8 @@ import org.springframework.security.core.userdetails.User;
|
|||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||||
|
|
||||||
|
import static org.springframework.security.config.Customizer.withDefaults;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Rob Winch
|
* @author Rob Winch
|
||||||
*/
|
*/
|
||||||
@ -32,15 +34,14 @@ public class SecurityConfig {
|
|||||||
@Bean
|
@Bean
|
||||||
SecurityWebFilterChain configure(ServerHttpSecurity http) throws Exception {
|
SecurityWebFilterChain configure(ServerHttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
.authorizeExchange()
|
.authorizeExchange(exchanges ->
|
||||||
.pathMatchers("/", "/public/**").permitAll()
|
exchanges
|
||||||
.anyExchange().authenticated()
|
.pathMatchers("/", "/public/**").permitAll()
|
||||||
.and()
|
.anyExchange().authenticated()
|
||||||
.oauth2Login()
|
)
|
||||||
.and()
|
.oauth2Login(withDefaults())
|
||||||
.formLogin()
|
.formLogin(withDefaults())
|
||||||
.and()
|
.oauth2Client(withDefaults());
|
||||||
.oauth2Client();
|
|
||||||
return http.build();
|
return http.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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -24,6 +24,8 @@ import org.springframework.security.core.userdetails.User;
|
|||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||||
|
|
||||||
|
import static org.springframework.security.config.Customizer.withDefaults;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Rob Winch
|
* @author Rob Winch
|
||||||
* @since 5.0
|
* @since 5.0
|
||||||
@ -42,15 +44,18 @@ public class WebfluxFormSecurityConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
.authorizeExchange()
|
.authorizeExchange(exchanges ->
|
||||||
.pathMatchers("/login").permitAll()
|
exchanges
|
||||||
.anyExchange().authenticated()
|
.pathMatchers("/login").permitAll()
|
||||||
.and()
|
.anyExchange().authenticated()
|
||||||
.httpBasic().and()
|
)
|
||||||
.formLogin()
|
.httpBasic(withDefaults())
|
||||||
.loginPage("/login");
|
.formLogin(formLogin ->
|
||||||
|
formLogin
|
||||||
|
.loginPage("/login")
|
||||||
|
);
|
||||||
return http.build();
|
return http.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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -25,6 +25,8 @@ import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
|
|||||||
import org.springframework.security.core.userdetails.User;
|
import org.springframework.security.core.userdetails.User;
|
||||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||||
|
|
||||||
|
import static org.springframework.security.config.Customizer.withDefaults;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Alexey Nesterov
|
* @author Alexey Nesterov
|
||||||
* @since 5.2
|
* @since 5.2
|
||||||
@ -40,13 +42,14 @@ public class WebfluxX509Application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
|
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) throws Exception {
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
http
|
http
|
||||||
.x509()
|
.x509(withDefaults())
|
||||||
.and()
|
.authorizeExchange(exchanges ->
|
||||||
.authorizeExchange()
|
exchanges
|
||||||
.anyExchange().authenticated();
|
.anyExchange().authenticated()
|
||||||
|
);
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
|
|
||||||
return http.build();
|
return http.build();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user