spring security ServerHttpSecurity 源码
spring security ServerHttpSecurity 代码
文件路径:/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java
/*
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.web.server;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.security.interfaces.RSAPublicKey;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.Function;
import java.util.function.Supplier;
import reactor.core.publisher.Mono;
import reactor.util.context.Context;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.DelegatingReactiveAuthenticationManager;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.ReactiveAuthenticationManagerResolver;
import org.springframework.security.authorization.AuthenticatedReactiveAuthorizationManager;
import org.springframework.security.authorization.AuthorityReactiveAuthorizationManager;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
import org.springframework.security.oauth2.client.InMemoryReactiveOAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeReactiveAuthenticationManager;
import org.springframework.security.oauth2.client.authentication.OAuth2LoginReactiveAuthenticationManager;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
import org.springframework.security.oauth2.client.endpoint.ReactiveOAuth2AccessTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.WebClientReactiveAuthorizationCodeTokenResponseClient;
import org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeReactiveAuthenticationManager;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcReactiveOAuth2UserService;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.userinfo.DefaultReactiveOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.ReactiveOAuth2UserService;
import org.springframework.security.oauth2.client.web.server.AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.client.web.server.OAuth2AuthorizationCodeGrantWebFilter;
import org.springframework.security.oauth2.client.web.server.OAuth2AuthorizationRequestRedirectWebFilter;
import org.springframework.security.oauth2.client.web.server.ServerAuthorizationRequestRepository;
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizationCodeAuthenticationTokenConverter;
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizationRequestResolver;
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.client.web.server.WebSessionOAuth2ServerAuthorizationRequestRepository;
import org.springframework.security.oauth2.client.web.server.authentication.OAuth2LoginAuthenticationWebFilter;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoderFactory;
import org.springframework.security.oauth2.server.resource.authentication.JwtReactiveAuthenticationManager;
import org.springframework.security.oauth2.server.resource.authentication.OpaqueTokenReactiveAuthenticationManager;
import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.introspection.NimbusReactiveOpaqueTokenIntrospector;
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector;
import org.springframework.security.oauth2.server.resource.web.access.server.BearerTokenServerAccessDeniedHandler;
import org.springframework.security.oauth2.server.resource.web.server.BearerTokenServerAuthenticationEntryPoint;
import org.springframework.security.oauth2.server.resource.web.server.ServerBearerTokenAuthenticationConverter;
import org.springframework.security.web.PortMapper;
import org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor;
import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor;
import org.springframework.security.web.server.DefaultServerRedirectStrategy;
import org.springframework.security.web.server.DelegatingServerAuthenticationEntryPoint;
import org.springframework.security.web.server.DelegatingServerAuthenticationEntryPoint.DelegateEntry;
import org.springframework.security.web.server.ExchangeMatcherRedirectWebFilter;
import org.springframework.security.web.server.MatcherSecurityWebFilterChain;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
import org.springframework.security.web.server.ServerRedirectStrategy;
import org.springframework.security.web.server.authentication.AnonymousAuthenticationWebFilter;
import org.springframework.security.web.server.authentication.AuthenticationConverterServerWebExchangeMatcher;
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
import org.springframework.security.web.server.authentication.HttpBasicServerAuthenticationEntryPoint;
import org.springframework.security.web.server.authentication.HttpStatusServerEntryPoint;
import org.springframework.security.web.server.authentication.ReactivePreAuthenticatedAuthenticationManager;
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationEntryPoint;
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationFailureHandler;
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationSuccessHandler;
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
import org.springframework.security.web.server.authentication.ServerAuthenticationEntryPointFailureHandler;
import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler;
import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;
import org.springframework.security.web.server.authentication.ServerFormLoginAuthenticationConverter;
import org.springframework.security.web.server.authentication.ServerHttpBasicAuthenticationConverter;
import org.springframework.security.web.server.authentication.ServerX509AuthenticationConverter;
import org.springframework.security.web.server.authentication.logout.DelegatingServerLogoutHandler;
import org.springframework.security.web.server.authentication.logout.LogoutWebFilter;
import org.springframework.security.web.server.authentication.logout.SecurityContextServerLogoutHandler;
import org.springframework.security.web.server.authentication.logout.ServerLogoutHandler;
import org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler;
import org.springframework.security.web.server.authorization.AuthorizationContext;
import org.springframework.security.web.server.authorization.AuthorizationWebFilter;
import org.springframework.security.web.server.authorization.DelegatingReactiveAuthorizationManager;
import org.springframework.security.web.server.authorization.ExceptionTranslationWebFilter;
import org.springframework.security.web.server.authorization.IpAddressReactiveAuthorizationManager;
import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler;
import org.springframework.security.web.server.authorization.ServerWebExchangeDelegatingServerAccessDeniedHandler;
import org.springframework.security.web.server.context.NoOpServerSecurityContextRepository;
import org.springframework.security.web.server.context.ReactorContextWebFilter;
import org.springframework.security.web.server.context.SecurityContextServerWebExchangeWebFilter;
import org.springframework.security.web.server.context.ServerSecurityContextRepository;
import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository;
import org.springframework.security.web.server.csrf.CsrfServerLogoutHandler;
import org.springframework.security.web.server.csrf.CsrfWebFilter;
import org.springframework.security.web.server.csrf.ServerCsrfTokenRepository;
import org.springframework.security.web.server.csrf.WebSessionServerCsrfTokenRepository;
import org.springframework.security.web.server.header.CacheControlServerHttpHeadersWriter;
import org.springframework.security.web.server.header.CompositeServerHttpHeadersWriter;
import org.springframework.security.web.server.header.ContentSecurityPolicyServerHttpHeadersWriter;
import org.springframework.security.web.server.header.ContentTypeOptionsServerHttpHeadersWriter;
import org.springframework.security.web.server.header.CrossOriginEmbedderPolicyServerHttpHeadersWriter;
import org.springframework.security.web.server.header.CrossOriginEmbedderPolicyServerHttpHeadersWriter.CrossOriginEmbedderPolicy;
import org.springframework.security.web.server.header.CrossOriginOpenerPolicyServerHttpHeadersWriter;
import org.springframework.security.web.server.header.CrossOriginOpenerPolicyServerHttpHeadersWriter.CrossOriginOpenerPolicy;
import org.springframework.security.web.server.header.CrossOriginResourcePolicyServerHttpHeadersWriter;
import org.springframework.security.web.server.header.CrossOriginResourcePolicyServerHttpHeadersWriter.CrossOriginResourcePolicy;
import org.springframework.security.web.server.header.FeaturePolicyServerHttpHeadersWriter;
import org.springframework.security.web.server.header.HttpHeaderWriterWebFilter;
import org.springframework.security.web.server.header.PermissionsPolicyServerHttpHeadersWriter;
import org.springframework.security.web.server.header.ReferrerPolicyServerHttpHeadersWriter;
import org.springframework.security.web.server.header.ReferrerPolicyServerHttpHeadersWriter.ReferrerPolicy;
import org.springframework.security.web.server.header.ServerHttpHeadersWriter;
import org.springframework.security.web.server.header.StrictTransportSecurityServerHttpHeadersWriter;
import org.springframework.security.web.server.header.XFrameOptionsServerHttpHeadersWriter;
import org.springframework.security.web.server.header.XXssProtectionServerHttpHeadersWriter;
import org.springframework.security.web.server.savedrequest.NoOpServerRequestCache;
import org.springframework.security.web.server.savedrequest.ServerRequestCache;
import org.springframework.security.web.server.savedrequest.ServerRequestCacheWebFilter;
import org.springframework.security.web.server.savedrequest.WebSessionServerRequestCache;
import org.springframework.security.web.server.transport.HttpsRedirectWebFilter;
import org.springframework.security.web.server.ui.LoginPageGeneratingWebFilter;
import org.springframework.security.web.server.ui.LogoutPageGeneratingWebFilter;
import org.springframework.security.web.server.util.matcher.AndServerWebExchangeMatcher;
import org.springframework.security.web.server.util.matcher.MediaTypeServerWebExchangeMatcher;
import org.springframework.security.web.server.util.matcher.NegatedServerWebExchangeMatcher;
import org.springframework.security.web.server.util.matcher.OrServerWebExchangeMatcher;
import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcherEntry;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.web.cors.reactive.CorsConfigurationSource;
import org.springframework.web.cors.reactive.CorsProcessor;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.DefaultCorsProcessor;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
/**
* A {@link ServerHttpSecurity} is similar to Spring Security's {@code HttpSecurity} but
* for WebFlux. It allows configuring web based security for specific http requests. By
* default it will be applied to all requests, but can be restricted using
* {@link #securityMatcher(ServerWebExchangeMatcher)} or other similar methods.
*
* A minimal configuration can be found below:
*
* <pre class="code">
* @Configuration
* @EnableWebFluxSecurity
* public class MyMinimalSecurityConfiguration {
*
* @Bean
* public MapReactiveUserDetailsService userDetailsService() {
* UserDetails user = User.withDefaultPasswordEncoder()
* .username("user")
* .password("password")
* .roles("USER")
* .build();
* return new MapReactiveUserDetailsService(user);
* }
* }
* </pre>
*
* Below is the same as our minimal configuration, but explicitly declaring the
* {@code ServerHttpSecurity}.
*
* <pre class="code">
* @Configuration
* @EnableWebFluxSecurity
* public class MyExplicitSecurityConfiguration {
*
* @Bean
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
* http
* .authorizeExchange()
* .anyExchange().authenticated()
* .and()
* .httpBasic().and()
* .formLogin();
* return http.build();
* }
*
* @Bean
* public MapReactiveUserDetailsService userDetailsService() {
* UserDetails user = User.withDefaultPasswordEncoder()
* .username("user")
* .password("password")
* .roles("USER")
* .build();
* return new MapReactiveUserDetailsService(user);
* }
* }
* </pre>
*
* @author Rob Winch
* @author Vedran Pavic
* @author Rafiullah Hamedy
* @author Eddú Meléndez
* @author Joe Grandja
* @author Parikshit Dutta
* @author Ankur Pathak
* @author Alexey Nesterov
* @since 5.0
*/
public class ServerHttpSecurity {
private ServerWebExchangeMatcher securityMatcher = ServerWebExchangeMatchers.anyExchange();
private AuthorizeExchangeSpec authorizeExchange;
private HttpsRedirectSpec httpsRedirectSpec;
private HeaderSpec headers = new HeaderSpec();
private CsrfSpec csrf = new CsrfSpec();
private CorsSpec cors = new CorsSpec();
private ExceptionHandlingSpec exceptionHandling = new ExceptionHandlingSpec();
private HttpBasicSpec httpBasic;
private PasswordManagementSpec passwordManagement;
private X509Spec x509;
private final RequestCacheSpec requestCache = new RequestCacheSpec();
private FormLoginSpec formLogin;
private OAuth2LoginSpec oauth2Login;
private OAuth2ResourceServerSpec resourceServer;
private OAuth2ClientSpec client;
private LogoutSpec logout = new LogoutSpec();
private LoginPageSpec loginPage = new LoginPageSpec();
private ReactiveAuthenticationManager authenticationManager;
private ServerSecurityContextRepository securityContextRepository;
private ServerAuthenticationEntryPoint authenticationEntryPoint;
private List<DelegateEntry> defaultEntryPoints = new ArrayList<>();
private ServerAccessDeniedHandler accessDeniedHandler;
private List<ServerWebExchangeDelegatingServerAccessDeniedHandler.DelegateEntry> defaultAccessDeniedHandlers = new ArrayList<>();
private List<WebFilter> webFilters = new ArrayList<>();
private ApplicationContext context;
private Throwable built;
private AnonymousSpec anonymous;
protected ServerHttpSecurity() {
}
/**
* The ServerExchangeMatcher that determines which requests apply to this HttpSecurity
* instance.
* @param matcher the ServerExchangeMatcher that determines which requests apply to
* this HttpSecurity instance. Default is all requests.
* @return the {@link ServerHttpSecurity} to continue configuring
*/
public ServerHttpSecurity securityMatcher(ServerWebExchangeMatcher matcher) {
Assert.notNull(matcher, "matcher cannot be null");
this.securityMatcher = matcher;
return this;
}
/**
* Adds a {@link WebFilter} at a specific position.
* @param webFilter the {@link WebFilter} to add
* @param order the place to insert the {@link WebFilter}
* @return the {@link ServerHttpSecurity} to continue configuring
*/
public ServerHttpSecurity addFilterAt(WebFilter webFilter, SecurityWebFiltersOrder order) {
this.webFilters.add(new OrderedWebFilter(webFilter, order.getOrder()));
return this;
}
/**
* Adds a {@link WebFilter} before specific position.
* @param webFilter the {@link WebFilter} to add
* @param order the place before which to insert the {@link WebFilter}
* @return the {@link ServerHttpSecurity} to continue configuring
* @since 5.2.0
*/
public ServerHttpSecurity addFilterBefore(WebFilter webFilter, SecurityWebFiltersOrder order) {
this.webFilters.add(new OrderedWebFilter(webFilter, order.getOrder() - 1));
return this;
}
/**
* Adds a {@link WebFilter} after specific position.
* @param webFilter the {@link WebFilter} to add
* @param order the place after which to insert the {@link WebFilter}
* @return the {@link ServerHttpSecurity} to continue configuring
* @since 5.2.0
*/
public ServerHttpSecurity addFilterAfter(WebFilter webFilter, SecurityWebFiltersOrder order) {
this.webFilters.add(new OrderedWebFilter(webFilter, order.getOrder() + 1));
return this;
}
/**
* Gets the ServerExchangeMatcher that determines which requests apply to this
* HttpSecurity instance.
* @return the ServerExchangeMatcher that determines which requests apply to this
* HttpSecurity instance.
*/
private ServerWebExchangeMatcher getSecurityMatcher() {
return this.securityMatcher;
}
/**
* The strategy used with {@code ReactorContextWebFilter}. It does impact how the
* {@code SecurityContext} is saved which is configured on a per
* {@link AuthenticationWebFilter} basis.
* @param securityContextRepository the repository to use
* @return the {@link ServerHttpSecurity} to continue configuring
*/
public ServerHttpSecurity securityContextRepository(ServerSecurityContextRepository securityContextRepository) {
Assert.notNull(securityContextRepository, "securityContextRepository cannot be null");
this.securityContextRepository = securityContextRepository;
return this;
}
/**
* Configures HTTPS redirection rules. If the default is used:
*
* <pre class="code">
* @Bean
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
* http
* // ...
* .redirectToHttps();
* 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) {
* http
* // ...
* .redirectToHttps()
* .httpsRedirectWhen((serverWebExchange) ->
* serverWebExchange.getRequest().getHeaders().containsKey("X-Requires-Https"))
* return http.build();
* }
* </pre>
* @return the {@link HttpsRedirectSpec} to customize
*/
public HttpsRedirectSpec redirectToHttps() {
this.httpsRedirectSpec = new 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) {
* 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
*/
public ServerHttpSecurity redirectToHttps(Customizer<HttpsRedirectSpec> httpsRedirectCustomizer) {
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> which is enabled by default. You can disable it using:
*
* <pre class="code">
* @Bean
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
* http
* // ...
* .csrf().disabled();
* return http.build();
* }
* </pre>
*
* Additional configuration options can be seen below:
*
*
* <pre class="code">
* @Bean
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
* http
* // ...
* .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>
* @return the {@link CsrfSpec} to customize
*/
public CsrfSpec csrf() {
if (this.csrf == null) {
this.csrf = new CsrfSpec();
}
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) {
* 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) {
* 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
*/
public ServerHttpSecurity csrf(Customizer<CsrfSpec> csrfCustomizer) {
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 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.
* @return the {@link CorsSpec} to customize
*/
public CorsSpec cors() {
if (this.cors == null) {
this.cors = new CorsSpec();
}
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
*/
public ServerHttpSecurity cors(Customizer<CorsSpec> corsCustomizer) {
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.
*
* <pre class="code">
* @Bean
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
* http
* // ...
* .anonymous().key("key")
* .authorities("ROLE_ANONYMOUS");
* return http.build();
* }
* </pre>
* @return the {@link AnonymousSpec} to customize
* @since 5.2.0
*/
public AnonymousSpec anonymous() {
if (this.anonymous == null) {
this.anonymous = new AnonymousSpec();
}
return this.anonymous;
}
/**
* Enables and Configures anonymous authentication. Anonymous Authentication is
* disabled by default.
*
* <pre class="code">
* @Bean
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
* 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
*/
public ServerHttpSecurity anonymous(Customizer<AnonymousSpec> anonymousCustomizer) {
if (this.anonymous == null) {
this.anonymous = new AnonymousSpec();
}
anonymousCustomizer.customize(this.anonymous);
return this;
}
/**
* Configures HTTP Basic authentication. An example configuration is provided below:
*
* <pre class="code">
* @Bean
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
* http
* // ...
* .httpBasic()
* // used for authenticating the credentials
* .authenticationManager(authenticationManager)
* // Custom persistence of the authentication
* .securityContextRepository(securityContextRepository);
* return http.build();
* }
* </pre>
* @return the {@link HttpBasicSpec} to customize
*/
public HttpBasicSpec httpBasic() {
if (this.httpBasic == null) {
this.httpBasic = new HttpBasicSpec();
}
return this.httpBasic;
}
/**
* Configures HTTP Basic authentication. An example configuration is provided below:
*
* <pre class="code">
* @Bean
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
* 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
*/
public ServerHttpSecurity httpBasic(Customizer<HttpBasicSpec> httpBasicCustomizer) {
if (this.httpBasic == null) {
this.httpBasic = new HttpBasicSpec();
}
httpBasicCustomizer.customize(this.httpBasic);
return this;
}
/**
* Configures password management. An example configuration is provided below:
*
* <pre class="code">
* @Bean
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
* http
* // ...
* .passwordManagement();
* return http.build();
* }
* </pre>
* @return the {@link PasswordManagementSpec} to customize
* @since 5.6
*/
public PasswordManagementSpec passwordManagement() {
if (this.passwordManagement == null) {
this.passwordManagement = new PasswordManagementSpec();
}
return this.passwordManagement;
}
/**
* Configures password management. An example configuration is provided below:
*
* <pre class="code">
* @Bean
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
* http
* // ...
* .passwordManagement(passwordManagement ->
* // Custom change password page.
* passwordManagement.changePasswordPage("/custom-change-password-page")
* );
* return http.build();
* }
* </pre>
* @param passwordManagementCustomizer the {@link Customizer} to provide more options
* for the {@link PasswordManagementSpec}
* @return the {@link ServerHttpSecurity} to customize
* @since 5.6
*/
public ServerHttpSecurity passwordManagement(Customizer<PasswordManagementSpec> passwordManagementCustomizer) {
if (this.passwordManagement == null) {
this.passwordManagement = new PasswordManagementSpec();
}
passwordManagementCustomizer.customize(this.passwordManagement);
return this;
}
/**
* Configures form based authentication. An example configuration is provided below:
*
* <pre class="code">
* @Bean
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
* http
* // ...
* .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>
* @return the {@link FormLoginSpec} to customize
*/
public FormLoginSpec formLogin() {
if (this.formLogin == null) {
this.formLogin = new FormLoginSpec();
}
return this.formLogin;
}
/**
* Configures form based authentication. An example configuration is provided below:
*
* <pre class="code">
* @Bean
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
* 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
*/
public ServerHttpSecurity formLogin(Customizer<FormLoginSpec> formLoginCustomizer) {
if (this.formLogin == null) {
this.formLogin = new FormLoginSpec();
}
formLoginCustomizer.customize(this.formLogin);
return this;
}
/**
* Configures x509 authentication using a certificate provided by a client.
*
* <pre class="code">
* @Bean
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
* http
* .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.
* @return the {@link X509Spec} to customize
* @since 5.2
*/
public X509Spec x509() {
if (this.x509 == null) {
this.x509 = new X509Spec();
}
return this.x509;
}
/**
* Configures x509 authentication using a certificate provided by a client.
*
* <pre class="code">
* @Bean
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
* 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.
* @param x509Customizer the {@link Customizer} to provide more options for the
* {@link X509Spec}
* @return the {@link ServerHttpSecurity} to customize
* @since 5.2
*/
public ServerHttpSecurity x509(Customizer<X509Spec> x509Customizer) {
if (this.x509 == null) {
this.x509 = new X509Spec();
}
x509Customizer.customize(this.x509);
return this;
}
/**
* Configures authentication support using an OAuth 2.0 and/or OpenID Connect 1.0
* Provider.
*
* <pre class="code">
* @Bean
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
* http
* // ...
* .oauth2Login()
* .authenticationConverter(authenticationConverter)
* .authenticationManager(manager);
* return http.build();
* }
* </pre>
* @return the {@link OAuth2LoginSpec} to customize
*/
public OAuth2LoginSpec oauth2Login() {
if (this.oauth2Login == null) {
this.oauth2Login = new OAuth2LoginSpec();
}
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) {
* 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
*/
public ServerHttpSecurity oauth2Login(Customizer<OAuth2LoginSpec> oauth2LoginCustomizer) {
if (this.oauth2Login == null) {
this.oauth2Login = new OAuth2LoginSpec();
}
oauth2LoginCustomizer.customize(this.oauth2Login);
return this;
}
/**
* Configures the OAuth2 client.
*
* <pre class="code">
* @Bean
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
* http
* // ...
* .oauth2Client()
* .clientRegistrationRepository(clientRegistrationRepository)
* .authorizedClientRepository(authorizedClientRepository);
* return http.build();
* }
* </pre>
* @return the {@link OAuth2ClientSpec} to customize
*/
public OAuth2ClientSpec oauth2Client() {
if (this.client == null) {
this.client = new OAuth2ClientSpec();
}
return this.client;
}
/**
* Configures the OAuth2 client.
*
* <pre class="code">
* @Bean
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
* 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
*/
public ServerHttpSecurity oauth2Client(Customizer<OAuth2ClientSpec> oauth2ClientCustomizer) {
if (this.client == null) {
this.client = new OAuth2ClientSpec();
}
oauth2ClientCustomizer.customize(this.client);
return this;
}
/**
* Configures OAuth 2.0 Resource Server support.
*
* <pre class="code">
* @Bean
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
* http
* // ...
* .oauth2ResourceServer()
* .jwt()
* .publicKey(publicKey());
* return http.build();
* }
* </pre>
* @return the {@link OAuth2ResourceServerSpec} to customize
*/
public OAuth2ResourceServerSpec oauth2ResourceServer() {
if (this.resourceServer == null) {
this.resourceServer = new OAuth2ResourceServerSpec();
}
return this.resourceServer;
}
/**
* Configures OAuth 2.0 Resource Server support.
*
* <pre class="code">
* @Bean
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
* 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
*/
public ServerHttpSecurity oauth2ResourceServer(
Customizer<OAuth2ResourceServerSpec> oauth2ResourceServerCustomizer) {
if (this.resourceServer == null) {
this.resourceServer = new OAuth2ResourceServerSpec();
}
oauth2ResourceServerCustomizer.customize(this.resourceServer);
return this;
}
/**
* 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) {
* http
* // ...
* .headers()
* // customize frame options to be same origin
* .frameOptions()
* .mode(XFrameOptionsServerHttpHeadersWriter.Mode.SAMEORIGIN)
* .and()
* // disable cache control
* .cache().disable();
* return http.build();
* }
* </pre>
* @return the {@link HeaderSpec} to customize
*/
public HeaderSpec headers() {
if (this.headers == null) {
this.headers = new HeaderSpec();
}
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) {
* 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
*/
public ServerHttpSecurity headers(Customizer<HeaderSpec> headerCustomizer) {
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 be found below:
*
* <pre class="code">
* @Bean
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
* http
* // ...
* .exceptionHandling()
* // customize how to request for authentication
* .authenticationEntryPoint(entryPoint);
* return http.build();
* }
* </pre>
* @return the {@link ExceptionHandlingSpec} to customize
*/
public ExceptionHandlingSpec exceptionHandling() {
if (this.exceptionHandling == null) {
this.exceptionHandling = new ExceptionHandlingSpec();
}
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) {
* 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
*/
public ServerHttpSecurity exceptionHandling(Customizer<ExceptionHandlingSpec> exceptionHandlingCustomizer) {
if (this.exceptionHandling == null) {
this.exceptionHandling = new ExceptionHandlingSpec();
}
exceptionHandlingCustomizer.customize(this.exceptionHandling);
return this;
}
/**
* Configures authorization. An example configuration can be found below:
*
* <pre class="code">
* @Bean
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
* http
* // ...
* .authorizeExchange()
* // 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>
* @return the {@link AuthorizeExchangeSpec} to customize
*/
public AuthorizeExchangeSpec authorizeExchange() {
if (this.authorizeExchange == null) {
this.authorizeExchange = new AuthorizeExchangeSpec();
}
return this.authorizeExchange;
}
/**
* Configures authorization. An example configuration can be found below:
*
* <pre class="code">
* @Bean
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
* 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
*/
public ServerHttpSecurity authorizeExchange(Customizer<AuthorizeExchangeSpec> authorizeExchangeCustomizer) {
if (this.authorizeExchange == null) {
this.authorizeExchange = new AuthorizeExchangeSpec();
}
authorizeExchangeCustomizer.customize(this.authorizeExchange);
return this;
}
/**
* Configures log out. An example configuration can be found below:
*
* <pre class="code">
* @Bean
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
* http
* // ...
* .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>
* @return the {@link LogoutSpec} to customize
*/
public LogoutSpec logout() {
if (this.logout == null) {
this.logout = new LogoutSpec();
}
return this.logout;
}
/**
* Configures log out. An example configuration can be found below:
*
* <pre class="code">
* @Bean
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
* 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
*/
public ServerHttpSecurity logout(Customizer<LogoutSpec> logoutCustomizer) {
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 that the request can be replayed after authentication.
* An example configuration can be found below:
*
* <pre class="code">
* @Bean
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
* http
* // ...
* .requestCache()
* // configures how the request is cached
* .requestCache(requestCache);
* return http.build();
* }
* </pre>
* @return the {@link RequestCacheSpec} to customize
*/
public RequestCacheSpec 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) {
* 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
*/
public ServerHttpSecurity requestCache(Customizer<RequestCacheSpec> requestCacheCustomizer) {
requestCacheCustomizer.customize(this.requestCache);
return this;
}
/**
* Configure the default authentication manager.
* @param manager the authentication manager to use
* @return the {@code ServerHttpSecurity} to customize
*/
public ServerHttpSecurity authenticationManager(ReactiveAuthenticationManager manager) {
this.authenticationManager = manager;
return this;
}
/**
* Builds the {@link SecurityWebFilterChain}
* @return the {@link SecurityWebFilterChain}
*/
public SecurityWebFilterChain build() {
if (this.built != null) {
throw new IllegalStateException(
"This has already been built with the following stacktrace. " + buildToString());
}
this.built = new RuntimeException("First Build Invocation").fillInStackTrace();
if (this.headers != null) {
this.headers.configure(this);
}
WebFilter securityContextRepositoryWebFilter = securityContextRepositoryWebFilter();
this.webFilters.add(securityContextRepositoryWebFilter);
if (this.httpsRedirectSpec != null) {
this.httpsRedirectSpec.configure(this);
}
if (this.x509 != null) {
this.x509.configure(this);
}
if (this.csrf != null) {
this.csrf.configure(this);
}
if (this.cors != null) {
this.cors.configure(this);
}
if (this.httpBasic != null) {
if (this.httpBasic.authenticationManager == null) {
this.httpBasic.authenticationManager(this.authenticationManager);
}
if (this.httpBasic.securityContextRepository != null) {
this.httpBasic.securityContextRepository(this.httpBasic.securityContextRepository);
}
else if (this.securityContextRepository != null) {
this.httpBasic.securityContextRepository(this.securityContextRepository);
}
else {
this.httpBasic.securityContextRepository(NoOpServerSecurityContextRepository.getInstance());
}
this.httpBasic.configure(this);
}
if (this.passwordManagement != null) {
this.passwordManagement.configure(this);
}
if (this.formLogin != null) {
if (this.formLogin.authenticationManager == null) {
this.formLogin.authenticationManager(this.authenticationManager);
}
if (this.formLogin.securityContextRepository != null) {
this.formLogin.securityContextRepository(this.formLogin.securityContextRepository);
}
else if (this.securityContextRepository != null) {
this.formLogin.securityContextRepository(this.securityContextRepository);
}
else {
this.formLogin.securityContextRepository(new WebSessionServerSecurityContextRepository());
}
this.formLogin.configure(this);
}
if (this.oauth2Login != null) {
if (this.oauth2Login.securityContextRepository != null) {
this.oauth2Login.securityContextRepository(this.oauth2Login.securityContextRepository);
}
else if (this.securityContextRepository != null) {
this.oauth2Login.securityContextRepository(this.securityContextRepository);
}
else {
this.oauth2Login.securityContextRepository(new WebSessionServerSecurityContextRepository());
}
this.oauth2Login.configure(this);
}
if (this.resourceServer != null) {
this.resourceServer.configure(this);
}
if (this.client != null) {
this.client.configure(this);
}
if (this.anonymous != null) {
this.anonymous.configure(this);
}
this.loginPage.configure(this);
if (this.logout != null) {
this.logout.configure(this);
}
this.requestCache.configure(this);
this.addFilterAt(new SecurityContextServerWebExchangeWebFilter(),
SecurityWebFiltersOrder.SECURITY_CONTEXT_SERVER_WEB_EXCHANGE);
if (this.authorizeExchange != null) {
ServerAuthenticationEntryPoint authenticationEntryPoint = getAuthenticationEntryPoint();
ExceptionTranslationWebFilter exceptionTranslationWebFilter = new ExceptionTranslationWebFilter();
if (authenticationEntryPoint != null) {
exceptionTranslationWebFilter.setAuthenticationEntryPoint(authenticationEntryPoint);
}
ServerAccessDeniedHandler accessDeniedHandler = getAccessDeniedHandler();
if (accessDeniedHandler != null) {
exceptionTranslationWebFilter.setAccessDeniedHandler(accessDeniedHandler);
}
this.addFilterAt(exceptionTranslationWebFilter, SecurityWebFiltersOrder.EXCEPTION_TRANSLATION);
this.authorizeExchange.configure(this);
}
AnnotationAwareOrderComparator.sort(this.webFilters);
List<WebFilter> sortedWebFilters = new ArrayList<>();
this.webFilters.forEach((f) -> {
if (f instanceof OrderedWebFilter) {
f = ((OrderedWebFilter) f).webFilter;
}
sortedWebFilters.add(f);
});
sortedWebFilters.add(0, new ServerWebExchangeReactorContextWebFilter());
return new MatcherSecurityWebFilterChain(getSecurityMatcher(), sortedWebFilters);
}
private String buildToString() {
try (StringWriter writer = new StringWriter()) {
try (PrintWriter printer = new PrintWriter(writer)) {
printer.println();
printer.println();
this.built.printStackTrace(printer);
printer.println();
printer.println();
return writer.toString();
}
}
catch (IOException ex) {
throw new RuntimeException(ex);
}
}
private ServerAuthenticationEntryPoint getAuthenticationEntryPoint() {
if (this.authenticationEntryPoint != null || this.defaultEntryPoints.isEmpty()) {
return this.authenticationEntryPoint;
}
if (this.defaultEntryPoints.size() == 1) {
return this.defaultEntryPoints.get(0).getEntryPoint();
}
DelegatingServerAuthenticationEntryPoint result = new DelegatingServerAuthenticationEntryPoint(
this.defaultEntryPoints);
result.setDefaultEntryPoint(this.defaultEntryPoints.get(this.defaultEntryPoints.size() - 1).getEntryPoint());
return result;
}
private ServerAccessDeniedHandler getAccessDeniedHandler() {
if (this.accessDeniedHandler != null || this.defaultAccessDeniedHandlers.isEmpty()) {
return this.accessDeniedHandler;
}
if (this.defaultAccessDeniedHandlers.size() == 1) {
return this.defaultAccessDeniedHandlers.get(0).getAccessDeniedHandler();
}
ServerWebExchangeDelegatingServerAccessDeniedHandler result = new ServerWebExchangeDelegatingServerAccessDeniedHandler(
this.defaultAccessDeniedHandlers);
result.setDefaultAccessDeniedHandler(this.defaultAccessDeniedHandlers
.get(this.defaultAccessDeniedHandlers.size() - 1).getAccessDeniedHandler());
return result;
}
/**
* Creates a new instance.
* @return the new {@link ServerHttpSecurity} instance
*/
public static ServerHttpSecurity http() {
return new ServerHttpSecurity();
}
private WebFilter securityContextRepositoryWebFilter() {
ServerSecurityContextRepository repository = (this.securityContextRepository != null)
? this.securityContextRepository : new WebSessionServerSecurityContextRepository();
WebFilter result = new ReactorContextWebFilter(repository);
return new OrderedWebFilter(result, SecurityWebFiltersOrder.REACTOR_CONTEXT.getOrder());
}
private <T> T getBean(Class<T> beanClass) {
if (this.context == null) {
return null;
}
return this.context.getBean(beanClass);
}
private <T> T getBeanOrNull(Class<T> beanClass) {
return getBeanOrNull(ResolvableType.forClass(beanClass));
}
private <T> T getBeanOrNull(ResolvableType type) {
if (this.context == null) {
return null;
}
String[] names = this.context.getBeanNamesForType(type);
if (names.length == 1) {
return (T) this.context.getBean(names[0]);
}
return null;
}
private <T> String[] getBeanNamesForTypeOrEmpty(Class<T> beanClass) {
if (this.context == null) {
return new String[0];
}
return this.context.getBeanNamesForType(beanClass);
}
protected void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
}
/**
* Configures authorization
*
* @author Rob Winch
* @since 5.0
* @see #authorizeExchange()
*/
public class AuthorizeExchangeSpec extends AbstractServerWebExchangeMatcherRegistry<AuthorizeExchangeSpec.Access> {
private DelegatingReactiveAuthorizationManager.Builder managerBldr = DelegatingReactiveAuthorizationManager
.builder();
private ServerWebExchangeMatcher matcher;
private boolean anyExchangeRegistered;
/**
* Allows method chaining to continue configuring the {@link ServerHttpSecurity}
* @return the {@link ServerHttpSecurity} to continue configuring
*/
public ServerHttpSecurity and() {
return ServerHttpSecurity.this;
}
/**
* Disables authorization.
* @return the {@link Access} to continue configuring
*/
@Override
public Access anyExchange() {
Access result = super.anyExchange();
this.anyExchangeRegistered = true;
return result;
}
@Override
protected Access registerMatcher(ServerWebExchangeMatcher matcher) {
Assert.state(!this.anyExchangeRegistered, () -> "Cannot register " + matcher
+ " which would be unreachable because anyExchange() has already been registered.");
Assert.state(this.matcher == null,
() -> "The matcher " + matcher + " does not have an access rule defined");
this.matcher = matcher;
return new Access();
}
protected void configure(ServerHttpSecurity http) {
Assert.state(this.matcher == null,
() -> "The matcher " + this.matcher + " does not have an access rule defined");
AuthorizationWebFilter result = new AuthorizationWebFilter(this.managerBldr.build());
http.addFilterAt(result, SecurityWebFiltersOrder.AUTHORIZATION);
}
/**
* Configures the access for a particular set of exchanges.
*/
public final class Access {
/**
* Allow access for anyone
* @return the {@link AuthorizeExchangeSpec} to configure
*/
public AuthorizeExchangeSpec permitAll() {
return access((a, e) -> Mono.just(new AuthorizationDecision(true)));
}
/**
* Deny access for everyone
* @return the {@link AuthorizeExchangeSpec} to configure
*/
public AuthorizeExchangeSpec denyAll() {
return access((a, e) -> Mono.just(new AuthorizationDecision(false)));
}
/**
* Require a specific role. This is a shorcut for
* {@link #hasAuthority(String)}
* @param role the role (i.e. "USER" would require "ROLE_USER")
* @return the {@link AuthorizeExchangeSpec} to configure
*/
public AuthorizeExchangeSpec hasRole(String role) {
return access(AuthorityReactiveAuthorizationManager.hasRole(role));
}
/**
* Require any specific role. This is a shortcut for
* {@link #hasAnyAuthority(String...)}
* @param roles the roles (i.e. "USER" would require "ROLE_USER")
* @return the {@link AuthorizeExchangeSpec} to configure
*/
public AuthorizeExchangeSpec hasAnyRole(String... roles) {
return access(AuthorityReactiveAuthorizationManager.hasAnyRole(roles));
}
/**
* Require a specific authority.
* @param authority the authority to require (i.e. "USER" would require
* authority of "USER").
* @return the {@link AuthorizeExchangeSpec} to configure
*/
public AuthorizeExchangeSpec hasAuthority(String authority) {
return access(AuthorityReactiveAuthorizationManager.hasAuthority(authority));
}
/**
* Require any authority
* @param authorities the authorities to require (i.e. "USER" would require
* authority of "USER").
* @return the {@link AuthorizeExchangeSpec} to configure
*/
public AuthorizeExchangeSpec hasAnyAuthority(String... authorities) {
return access(AuthorityReactiveAuthorizationManager.hasAnyAuthority(authorities));
}
/**
* Require an authenticated user
* @return the {@link AuthorizeExchangeSpec} to configure
*/
public AuthorizeExchangeSpec authenticated() {
return access(AuthenticatedReactiveAuthorizationManager.authenticated());
}
/**
* Require a specific IP address or range using an IP/Netmask (e.g.
* 192.168.1.0/24).
* @param ipAddress the address or range of addresses from which the request
* must come.
* @return the {@link AuthorizeExchangeSpec} to configure
* @since 5.7
*/
public AuthorizeExchangeSpec hasIpAddress(String ipAddress) {
return access(IpAddressReactiveAuthorizationManager.hasIpAddress(ipAddress));
}
/**
* Allows plugging in a custom authorization strategy
* @param manager the authorization manager to use
* @return the {@link AuthorizeExchangeSpec} to configure
*/
public AuthorizeExchangeSpec access(ReactiveAuthorizationManager<AuthorizationContext> manager) {
AuthorizeExchangeSpec.this.managerBldr
.add(new ServerWebExchangeMatcherEntry<>(AuthorizeExchangeSpec.this.matcher, manager));
AuthorizeExchangeSpec.this.matcher = null;
return AuthorizeExchangeSpec.this;
}
}
}
/**
* Configures HTTPS redirection rules
*
* @author Josh Cummings
* @since 5.1
* @see #redirectToHttps()
*/
public class HttpsRedirectSpec {
private ServerWebExchangeMatcher serverWebExchangeMatcher;
private PortMapper portMapper;
/**
* Configures when this filter should redirect to https
*
* By default, the filter will redirect whenever an exchange's scheme is not https
* @param matchers the list of conditions that, when any are met, the filter
* should redirect to https
* @return the {@link HttpsRedirectSpec} for additional configuration
*/
public HttpsRedirectSpec httpsRedirectWhen(ServerWebExchangeMatcher... matchers) {
this.serverWebExchangeMatcher = new OrServerWebExchangeMatcher(matchers);
return this;
}
/**
* Configures when this filter should redirect to https
*
* By default, the filter will redirect whenever an exchange's scheme is not https
* @param when determines when to redirect to https
* @return the {@link HttpsRedirectSpec} for additional configuration
*/
public HttpsRedirectSpec httpsRedirectWhen(Function<ServerWebExchange, Boolean> when) {
ServerWebExchangeMatcher matcher = (e) -> when.apply(e) ? ServerWebExchangeMatcher.MatchResult.match()
: ServerWebExchangeMatcher.MatchResult.notMatch();
return httpsRedirectWhen(matcher);
}
/**
* Configures a custom HTTPS port to redirect to
* @param portMapper the {@link PortMapper} to use
* @return the {@link HttpsRedirectSpec} for additional configuration
*/
public HttpsRedirectSpec portMapper(PortMapper portMapper) {
this.portMapper = portMapper;
return this;
}
protected void configure(ServerHttpSecurity http) {
HttpsRedirectWebFilter httpsRedirectWebFilter = new HttpsRedirectWebFilter();
if (this.serverWebExchangeMatcher != null) {
httpsRedirectWebFilter.setRequiresHttpsRedirectMatcher(this.serverWebExchangeMatcher);
}
if (this.portMapper != null) {
httpsRedirectWebFilter.setPortMapper(this.portMapper);
}
http.addFilterAt(httpsRedirectWebFilter, SecurityWebFiltersOrder.HTTPS_REDIRECT);
}
/**
* Allows method chaining to continue configuring the {@link ServerHttpSecurity}
* @return the {@link ServerHttpSecurity} to continue configuring
*/
public ServerHttpSecurity and() {
return ServerHttpSecurity.this;
}
}
/**
* Configures <a href=
* "https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet">CSRF
* Protection</a>
*
* @author Rob Winch
* @since 5.0
* @see #csrf()
*/
public final class CsrfSpec {
private CsrfSpec() {
}
private CsrfWebFilter filter = new CsrfWebFilter();
private ServerCsrfTokenRepository csrfTokenRepository = new WebSessionServerCsrfTokenRepository();
private boolean specifiedRequireCsrfProtectionMatcher;
/**
* Configures the {@link ServerAccessDeniedHandler} used when a CSRF token is
* invalid. Default is to send an
* {@link org.springframework.http.HttpStatus#FORBIDDEN}.
* @param accessDeniedHandler the access denied handler.
* @return the {@link CsrfSpec} for additional configuration
*/
public CsrfSpec accessDeniedHandler(ServerAccessDeniedHandler accessDeniedHandler) {
this.filter.setAccessDeniedHandler(accessDeniedHandler);
return this;
}
/**
* Configures the {@link ServerCsrfTokenRepository} used to persist the CSRF
* Token. Default is
* {@link org.springframework.security.web.server.csrf.WebSessionServerCsrfTokenRepository}.
* @param csrfTokenRepository the repository to use
* @return the {@link CsrfSpec} for additional configuration
*/
public CsrfSpec csrfTokenRepository(ServerCsrfTokenRepository csrfTokenRepository) {
this.csrfTokenRepository = csrfTokenRepository;
return this;
}
/**
* Configures the {@link ServerWebExchangeMatcher} used to determine when CSRF
* protection is enabled. Default is PUT, POST, DELETE requests.
* @param requireCsrfProtectionMatcher the matcher to use
* @return the {@link CsrfSpec} for additional configuration
*/
public CsrfSpec requireCsrfProtectionMatcher(ServerWebExchangeMatcher requireCsrfProtectionMatcher) {
this.filter.setRequireCsrfProtectionMatcher(requireCsrfProtectionMatcher);
this.specifiedRequireCsrfProtectionMatcher = true;
return this;
}
/**
* Specifies if {@link CsrfWebFilter} should try to resolve the actual CSRF token
* from the body of multipart data requests.
* @param enabled true if should read from multipart form body, else false.
* Default is false
* @return the {@link CsrfSpec} for additional configuration
*/
public CsrfSpec tokenFromMultipartDataEnabled(boolean enabled) {
this.filter.setTokenFromMultipartDataEnabled(enabled);
return this;
}
/**
* Allows method chaining to continue configuring the {@link ServerHttpSecurity}
* @return the {@link ServerHttpSecurity} to continue configuring
*/
public ServerHttpSecurity and() {
return ServerHttpSecurity.this;
}
/**
* Disables CSRF Protection. Disabling CSRF Protection is only recommended when
* the application is never used within a browser.
* @return the {@link ServerHttpSecurity} to continue configuring
*/
public ServerHttpSecurity disable() {
ServerHttpSecurity.this.csrf = null;
return ServerHttpSecurity.this;
}
protected void configure(ServerHttpSecurity http) {
if (this.csrfTokenRepository != null) {
this.filter.setCsrfTokenRepository(this.csrfTokenRepository);
if (ServerHttpSecurity.this.logout != null) {
ServerHttpSecurity.this.logout
.addLogoutHandler(new CsrfServerLogoutHandler(this.csrfTokenRepository));
}
}
http.addFilterAt(this.filter, SecurityWebFiltersOrder.CSRF);
}
}
/**
* Configures exception handling
*
* @author Rob Winch
* @since 5.0
* @see #exceptionHandling()
*/
public final class ExceptionHandlingSpec {
private ExceptionHandlingSpec() {
}
/**
* Configures what to do when the application request authentication
* @param authenticationEntryPoint the entry point to use
* @return the {@link ExceptionHandlingSpec} to configure
*/
public ExceptionHandlingSpec authenticationEntryPoint(ServerAuthenticationEntryPoint authenticationEntryPoint) {
ServerHttpSecurity.this.authenticationEntryPoint = authenticationEntryPoint;
return this;
}
/**
* Configures what to do when an authenticated user does not hold a required
* authority
* @param accessDeniedHandler the access denied handler to use
* @return the {@link ExceptionHandlingSpec} to configure
*
* @since 5.0.5
*/
public ExceptionHandlingSpec accessDeniedHandler(ServerAccessDeniedHandler accessDeniedHandler) {
ServerHttpSecurity.this.accessDeniedHandler = accessDeniedHandler;
return this;
}
/**
* Allows method chaining to continue configuring the {@link ServerHttpSecurity}
* @return the {@link ServerHttpSecurity} to continue configuring
*/
public ServerHttpSecurity and() {
return ServerHttpSecurity.this;
}
}
/**
* 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.
*
* @author Rob Winch
* @since 5.0
* @see #requestCache()
*/
public final class RequestCacheSpec {
private ServerRequestCache requestCache = new WebSessionServerRequestCache();
private RequestCacheSpec() {
}
/**
* Configures the cache used
* @param requestCache the request cache
* @return the {@link RequestCacheSpec} to configure
*/
public RequestCacheSpec requestCache(ServerRequestCache requestCache) {
Assert.notNull(requestCache, "requestCache cannot be null");
this.requestCache = requestCache;
return this;
}
protected void configure(ServerHttpSecurity http) {
ServerRequestCacheWebFilter filter = new ServerRequestCacheWebFilter();
filter.setRequestCache(this.requestCache);
http.addFilterAt(filter, SecurityWebFiltersOrder.SERVER_REQUEST_CACHE);
}
/**
* Allows method chaining to continue configuring the {@link ServerHttpSecurity}
* @return the {@link ServerHttpSecurity} to continue configuring
*/
public ServerHttpSecurity and() {
return ServerHttpSecurity.this;
}
/**
* Disables the {@link RequestCacheSpec}
* @return the {@link ServerHttpSecurity} to continue configuring
*/
public ServerHttpSecurity disable() {
this.requestCache = NoOpServerRequestCache.getInstance();
return and();
}
}
/**
* Configures HTTP Basic Authentication
*
* @author Rob Winch
* @since 5.0
* @see #httpBasic()
*/
public final class HttpBasicSpec {
private final ServerWebExchangeMatcher xhrMatcher = (exchange) -> Mono.just(exchange.getRequest().getHeaders())
.filter((h) -> h.getOrEmpty("X-Requested-With").contains("XMLHttpRequest"))
.flatMap((h) -> ServerWebExchangeMatcher.MatchResult.match())
.switchIfEmpty(ServerWebExchangeMatcher.MatchResult.notMatch());
private ReactiveAuthenticationManager authenticationManager;
private ServerSecurityContextRepository securityContextRepository;
private ServerAuthenticationEntryPoint entryPoint;
private HttpBasicSpec() {
List<DelegateEntry> entryPoints = new ArrayList<>();
entryPoints
.add(new DelegateEntry(this.xhrMatcher, new HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED)));
DelegatingServerAuthenticationEntryPoint defaultEntryPoint = new DelegatingServerAuthenticationEntryPoint(
entryPoints);
defaultEntryPoint.setDefaultEntryPoint(new HttpBasicServerAuthenticationEntryPoint());
this.entryPoint = defaultEntryPoint;
}
/**
* The {@link ReactiveAuthenticationManager} used to authenticate. Defaults to
* {@link ServerHttpSecurity#authenticationManager(ReactiveAuthenticationManager)}.
* @param authenticationManager the authentication manager to use
* @return the {@link HttpBasicSpec} to continue configuring
*/
public HttpBasicSpec authenticationManager(ReactiveAuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
return this;
}
/**
* The {@link ServerSecurityContextRepository} used to save the
* {@code Authentication}. Defaults to
* {@link NoOpServerSecurityContextRepository}. For the {@code SecurityContext} to
* be loaded on subsequent requests the {@link ReactorContextWebFilter} must be
* configured to be able to load the value (they are not implicitly linked).
* @param securityContextRepository the repository to use
* @return the {@link HttpBasicSpec} to continue configuring
*/
public HttpBasicSpec securityContextRepository(ServerSecurityContextRepository securityContextRepository) {
this.securityContextRepository = securityContextRepository;
return this;
}
/**
* Allows easily setting the entry point.
* @param authenticationEntryPoint the {@link ServerAuthenticationEntryPoint} to
* use
* @return {@link HttpBasicSpec} for additional customization
* @since 5.2.0
*/
public HttpBasicSpec authenticationEntryPoint(ServerAuthenticationEntryPoint authenticationEntryPoint) {
Assert.notNull(authenticationEntryPoint, "authenticationEntryPoint cannot be null");
this.entryPoint = authenticationEntryPoint;
return this;
}
/**
* Allows method chaining to continue configuring the {@link ServerHttpSecurity}
* @return the {@link ServerHttpSecurity} to continue configuring
*/
public ServerHttpSecurity and() {
return ServerHttpSecurity.this;
}
/**
* Disables HTTP Basic authentication.
* @return the {@link ServerHttpSecurity} to continue configuring
*/
public ServerHttpSecurity disable() {
ServerHttpSecurity.this.httpBasic = null;
return ServerHttpSecurity.this;
}
protected void configure(ServerHttpSecurity http) {
MediaTypeServerWebExchangeMatcher restMatcher = new MediaTypeServerWebExchangeMatcher(
MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_JSON,
MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_XML, MediaType.MULTIPART_FORM_DATA,
MediaType.TEXT_XML);
restMatcher.setIgnoredMediaTypes(Collections.singleton(MediaType.ALL));
ServerWebExchangeMatcher notHtmlMatcher = new NegatedServerWebExchangeMatcher(
new MediaTypeServerWebExchangeMatcher(MediaType.TEXT_HTML));
ServerWebExchangeMatcher restNotHtmlMatcher = new AndServerWebExchangeMatcher(
Arrays.asList(notHtmlMatcher, restMatcher));
ServerWebExchangeMatcher preferredMatcher = new OrServerWebExchangeMatcher(
Arrays.asList(this.xhrMatcher, restNotHtmlMatcher));
ServerHttpSecurity.this.defaultEntryPoints.add(new DelegateEntry(preferredMatcher, this.entryPoint));
AuthenticationWebFilter authenticationFilter = new AuthenticationWebFilter(this.authenticationManager);
authenticationFilter
.setAuthenticationFailureHandler(new ServerAuthenticationEntryPointFailureHandler(this.entryPoint));
authenticationFilter.setAuthenticationConverter(new ServerHttpBasicAuthenticationConverter());
authenticationFilter.setSecurityContextRepository(this.securityContextRepository);
http.addFilterAt(authenticationFilter, SecurityWebFiltersOrder.HTTP_BASIC);
}
}
/**
* Configures password management.
*
* @author Evgeniy Cheban
* @since 5.6
* @see #passwordManagement()
*/
public final class PasswordManagementSpec {
private static final String WELL_KNOWN_CHANGE_PASSWORD_PATTERN = "/.well-known/change-password";
private static final String DEFAULT_CHANGE_PASSWORD_PAGE = "/change-password";
private String changePasswordPage = DEFAULT_CHANGE_PASSWORD_PAGE;
/**
* Sets the change password page. Defaults to
* {@link PasswordManagementSpec#DEFAULT_CHANGE_PASSWORD_PAGE}.
* @param changePasswordPage the change password page
* @return the {@link PasswordManagementSpec} to continue configuring
*/
public PasswordManagementSpec changePasswordPage(String changePasswordPage) {
Assert.hasText(changePasswordPage, "changePasswordPage cannot be empty");
this.changePasswordPage = changePasswordPage;
return this;
}
/**
* Allows method chaining to continue configuring the {@link ServerHttpSecurity}.
* @return the {@link ServerHttpSecurity} to continue configuring
*/
public ServerHttpSecurity and() {
return ServerHttpSecurity.this;
}
protected void configure(ServerHttpSecurity http) {
ExchangeMatcherRedirectWebFilter changePasswordWebFilter = new ExchangeMatcherRedirectWebFilter(
new PathPatternParserServerWebExchangeMatcher(WELL_KNOWN_CHANGE_PASSWORD_PATTERN),
this.changePasswordPage);
http.addFilterBefore(changePasswordWebFilter, SecurityWebFiltersOrder.AUTHENTICATION);
}
private PasswordManagementSpec() {
}
}
/**
* Configures Form Based authentication
*
* @author Rob Winch
* @since 5.0
* @see #formLogin()
*/
public final class FormLoginSpec {
private final RedirectServerAuthenticationSuccessHandler defaultSuccessHandler = new RedirectServerAuthenticationSuccessHandler(
"/");
private RedirectServerAuthenticationEntryPoint defaultEntryPoint;
private ReactiveAuthenticationManager authenticationManager;
private ServerSecurityContextRepository securityContextRepository;
private ServerAuthenticationEntryPoint authenticationEntryPoint;
private boolean isEntryPointExplicit;
private ServerWebExchangeMatcher requiresAuthenticationMatcher;
private ServerAuthenticationFailureHandler authenticationFailureHandler;
private ServerAuthenticationSuccessHandler authenticationSuccessHandler = this.defaultSuccessHandler;
private FormLoginSpec() {
}
/**
* The {@link ReactiveAuthenticationManager} used to authenticate. Defaults to
* {@link ServerHttpSecurity#authenticationManager(ReactiveAuthenticationManager)}.
* @param authenticationManager the authentication manager to use
* @return the {@link FormLoginSpec} to continue configuring
*/
public FormLoginSpec authenticationManager(ReactiveAuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
return this;
}
/**
* The {@link ServerAuthenticationSuccessHandler} used after authentication
* success. Defaults to {@link RedirectServerAuthenticationSuccessHandler}.
* @param authenticationSuccessHandler the success handler to use
* @return the {@link FormLoginSpec} to continue configuring
*/
public FormLoginSpec authenticationSuccessHandler(
ServerAuthenticationSuccessHandler authenticationSuccessHandler) {
Assert.notNull(authenticationSuccessHandler, "authenticationSuccessHandler cannot be null");
this.authenticationSuccessHandler = authenticationSuccessHandler;
return this;
}
/**
* Configures the log in page to redirect to, the authentication failure page, and
* when authentication is performed. The default is that Spring Security will
* generate a log in page at "/login" and a log out page at "/logout". If this is
* customized:
* <ul>
* <li>The default log in & log out page are no longer provided</li>
* <li>The application must render a log in page at the provided URL</li>
* <li>The application must render an authentication error page at the provided
* URL + "?error"</li>
* <li>Authentication will occur for POST to the provided URL</li>
* </ul>
* @param loginPage the url to redirect to which provides a form to log in (i.e.
* "/login")
* @return the {@link FormLoginSpec} to continue configuring
* @see #authenticationEntryPoint(ServerAuthenticationEntryPoint)
* @see #requiresAuthenticationMatcher(ServerWebExchangeMatcher)
* @see #authenticationFailureHandler(ServerAuthenticationFailureHandler)
*/
public FormLoginSpec loginPage(String loginPage) {
this.defaultEntryPoint = new RedirectServerAuthenticationEntryPoint(loginPage);
this.authenticationEntryPoint = this.defaultEntryPoint;
if (this.requiresAuthenticationMatcher == null) {
this.requiresAuthenticationMatcher = ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, loginPage);
}
if (this.authenticationFailureHandler == null) {
this.authenticationFailureHandler = new RedirectServerAuthenticationFailureHandler(
loginPage + "?error");
}
return this;
}
/**
* How to request for authentication. The default is that Spring Security will
* generate a log in page at "/login".
* @param authenticationEntryPoint the entry point to use
* @return the {@link FormLoginSpec} to continue configuring
* @see #loginPage(String)
*/
public FormLoginSpec authenticationEntryPoint(ServerAuthenticationEntryPoint authenticationEntryPoint) {
this.authenticationEntryPoint = authenticationEntryPoint;
return this;
}
/**
* Configures when authentication is performed. The default is a POST to "/login".
* @param requiresAuthenticationMatcher the matcher to use
* @return the {@link FormLoginSpec} to continue configuring
* @see #loginPage(String)
*/
public FormLoginSpec requiresAuthenticationMatcher(ServerWebExchangeMatcher requiresAuthenticationMatcher) {
this.requiresAuthenticationMatcher = requiresAuthenticationMatcher;
return this;
}
/**
* Configures how a failed authentication is handled. The default is to redirect
* to "/login?error".
* @param authenticationFailureHandler the handler to use
* @return the {@link FormLoginSpec} to continue configuring
* @see #loginPage(String)
*/
public FormLoginSpec authenticationFailureHandler(
ServerAuthenticationFailureHandler authenticationFailureHandler) {
this.authenticationFailureHandler = authenticationFailureHandler;
return this;
}
/**
* The {@link ServerSecurityContextRepository} used to save the
* {@code Authentication}. Defaults to
* {@link WebSessionServerSecurityContextRepository}. For the
* {@code SecurityContext} to be loaded on subsequent requests the
* {@link ReactorContextWebFilter} must be configured to be able to load the value
* (they are not implicitly linked).
* @param securityContextRepository the repository to use
* @return the {@link FormLoginSpec} to continue configuring
*/
public FormLoginSpec securityContextRepository(ServerSecurityContextRepository securityContextRepository) {
this.securityContextRepository = securityContextRepository;
return this;
}
/**
* Allows method chaining to continue configuring the {@link ServerHttpSecurity}
* @return the {@link ServerHttpSecurity} to continue configuring
*/
public ServerHttpSecurity and() {
return ServerHttpSecurity.this;
}
/**
* Disables HTTP Basic authentication.
* @return the {@link ServerHttpSecurity} to continue configuring
*/
public ServerHttpSecurity disable() {
ServerHttpSecurity.this.formLogin = null;
return ServerHttpSecurity.this;
}
protected void configure(ServerHttpSecurity http) {
if (this.authenticationEntryPoint == null) {
this.isEntryPointExplicit = false;
loginPage("/login");
}
else {
this.isEntryPointExplicit = true;
}
if (http.requestCache != null) {
ServerRequestCache requestCache = http.requestCache.requestCache;
this.defaultSuccessHandler.setRequestCache(requestCache);
if (this.defaultEntryPoint != null) {
this.defaultEntryPoint.setRequestCache(requestCache);
}
}
MediaTypeServerWebExchangeMatcher htmlMatcher = new MediaTypeServerWebExchangeMatcher(MediaType.TEXT_HTML);
htmlMatcher.setIgnoredMediaTypes(Collections.singleton(MediaType.ALL));
ServerHttpSecurity.this.defaultEntryPoints.add(0,
new DelegateEntry(htmlMatcher, this.authenticationEntryPoint));
AuthenticationWebFilter authenticationFilter = new AuthenticationWebFilter(this.authenticationManager);
authenticationFilter.setRequiresAuthenticationMatcher(this.requiresAuthenticationMatcher);
authenticationFilter.setAuthenticationFailureHandler(this.authenticationFailureHandler);
authenticationFilter.setAuthenticationConverter(new ServerFormLoginAuthenticationConverter());
authenticationFilter.setAuthenticationSuccessHandler(this.authenticationSuccessHandler);
authenticationFilter.setSecurityContextRepository(this.securityContextRepository);
http.addFilterAt(authenticationFilter, SecurityWebFiltersOrder.FORM_LOGIN);
}
}
private final class LoginPageSpec {
private LoginPageSpec() {
}
protected void configure(ServerHttpSecurity http) {
if (http.authenticationEntryPoint != null) {
return;
}
if (http.formLogin != null && http.formLogin.isEntryPointExplicit) {
return;
}
LoginPageGeneratingWebFilter loginPage = null;
if (http.formLogin != null && !http.formLogin.isEntryPointExplicit) {
loginPage = new LoginPageGeneratingWebFilter();
loginPage.setFormLoginEnabled(true);
}
if (http.oauth2Login != null) {
Map<String, String> urlToText = http.oauth2Login.getLinks();
if (loginPage == null) {
loginPage = new LoginPageGeneratingWebFilter();
}
loginPage.setOauth2AuthenticationUrlToClientName(urlToText);
}
if (loginPage != null) {
http.addFilterAt(loginPage, SecurityWebFiltersOrder.LOGIN_PAGE_GENERATING);
if (http.logout != null) {
http.addFilterAt(new LogoutPageGeneratingWebFilter(),
SecurityWebFiltersOrder.LOGOUT_PAGE_GENERATING);
}
}
}
}
/**
* Configures HTTP Response Headers.
*
* @author Rob Winch
* @since 5.0
* @see #headers()
*/
public final class HeaderSpec {
private final List<ServerHttpHeadersWriter> writers;
private CacheControlServerHttpHeadersWriter cacheControl = new CacheControlServerHttpHeadersWriter();
private ContentTypeOptionsServerHttpHeadersWriter contentTypeOptions = new ContentTypeOptionsServerHttpHeadersWriter();
private StrictTransportSecurityServerHttpHeadersWriter hsts = new StrictTransportSecurityServerHttpHeadersWriter();
private XFrameOptionsServerHttpHeadersWriter frameOptions = new XFrameOptionsServerHttpHeadersWriter();
private XXssProtectionServerHttpHeadersWriter xss = new XXssProtectionServerHttpHeadersWriter();
private FeaturePolicyServerHttpHeadersWriter featurePolicy = new FeaturePolicyServerHttpHeadersWriter();
private PermissionsPolicyServerHttpHeadersWriter permissionsPolicy = new PermissionsPolicyServerHttpHeadersWriter();
private ContentSecurityPolicyServerHttpHeadersWriter contentSecurityPolicy = new ContentSecurityPolicyServerHttpHeadersWriter();
private ReferrerPolicyServerHttpHeadersWriter referrerPolicy = new ReferrerPolicyServerHttpHeadersWriter();
private CrossOriginOpenerPolicyServerHttpHeadersWriter crossOriginOpenerPolicy = new CrossOriginOpenerPolicyServerHttpHeadersWriter();
private CrossOriginEmbedderPolicyServerHttpHeadersWriter crossOriginEmbedderPolicy = new CrossOriginEmbedderPolicyServerHttpHeadersWriter();
private CrossOriginResourcePolicyServerHttpHeadersWriter crossOriginResourcePolicy = new CrossOriginResourcePolicyServerHttpHeadersWriter();
private HeaderSpec() {
this.writers = new ArrayList<>(Arrays.asList(this.cacheControl, this.contentTypeOptions, this.hsts,
this.frameOptions, this.xss, this.featurePolicy, this.permissionsPolicy, this.contentSecurityPolicy,
this.referrerPolicy, this.crossOriginOpenerPolicy, this.crossOriginEmbedderPolicy,
this.crossOriginResourcePolicy));
}
/**
* Allows method chaining to continue configuring the {@link ServerHttpSecurity}
* @return the {@link ServerHttpSecurity} to continue configuring
*/
public ServerHttpSecurity and() {
return ServerHttpSecurity.this;
}
/**
* Disables http response headers
* @return the {@link ServerHttpSecurity} to continue configuring
*/
public ServerHttpSecurity disable() {
ServerHttpSecurity.this.headers = null;
return ServerHttpSecurity.this;
}
/**
* Configures cache control headers
* @return the {@link CacheSpec} to configure
*/
public CacheSpec cache() {
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
*/
public HeaderSpec cache(Customizer<CacheSpec> cacheCustomizer) {
cacheCustomizer.customize(new CacheSpec());
return this;
}
/**
* Configures content type response headers
* @return the {@link ContentTypeOptionsSpec} to configure
*/
public ContentTypeOptionsSpec contentTypeOptions() {
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
*/
public HeaderSpec contentTypeOptions(Customizer<ContentTypeOptionsSpec> contentTypeOptionsCustomizer) {
contentTypeOptionsCustomizer.customize(new ContentTypeOptionsSpec());
return this;
}
/**
* Configures frame options response headers
* @return the {@link FrameOptionsSpec} to configure
*/
public FrameOptionsSpec frameOptions() {
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
*/
public HeaderSpec frameOptions(Customizer<FrameOptionsSpec> frameOptionsCustomizer) {
frameOptionsCustomizer.customize(new FrameOptionsSpec());
return this;
}
/**
* Configures custom headers writer
* @param serverHttpHeadersWriter the {@link ServerHttpHeadersWriter} to provide
* custom headers writer
* @return the {@link HeaderSpec} to customize
* @since 5.3.0
*/
public HeaderSpec writer(ServerHttpHeadersWriter serverHttpHeadersWriter) {
Assert.notNull(serverHttpHeadersWriter, "serverHttpHeadersWriter cannot be null");
this.writers.add(serverHttpHeadersWriter);
return this;
}
/**
* Configures the Strict Transport Security response headers
* @return the {@link HstsSpec} to configure
*/
public HstsSpec hsts() {
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
*/
public HeaderSpec hsts(Customizer<HstsSpec> hstsCustomizer) {
hstsCustomizer.customize(new HstsSpec());
return this;
}
protected void configure(ServerHttpSecurity http) {
ServerHttpHeadersWriter writer = new CompositeServerHttpHeadersWriter(this.writers);
HttpHeaderWriterWebFilter result = new HttpHeaderWriterWebFilter(writer);
http.addFilterAt(result, SecurityWebFiltersOrder.HTTP_HEADERS_WRITER);
}
/**
* Configures x-xss-protection response header.
* @return the {@link XssProtectionSpec} to configure
*/
public XssProtectionSpec xssProtection() {
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
*/
public HeaderSpec xssProtection(Customizer<XssProtectionSpec> xssProtectionCustomizer) {
xssProtectionCustomizer.customize(new XssProtectionSpec());
return this;
}
/**
* Configures {@code Content-Security-Policy} response header.
* @param policyDirectives the policy directive(s)
* @return the {@link ContentSecurityPolicySpec} to configure
*/
public ContentSecurityPolicySpec contentSecurityPolicy(String 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
*/
public HeaderSpec contentSecurityPolicy(Customizer<ContentSecurityPolicySpec> contentSecurityPolicyCustomizer) {
contentSecurityPolicyCustomizer.customize(new ContentSecurityPolicySpec());
return this;
}
/**
* Configures {@code Feature-Policy} response header.
* @param policyDirectives the policy
* @return the {@link FeaturePolicySpec} to configure
* @deprecated Use {@link #permissionsPolicy(Customizer)} instead.
*/
@Deprecated
public FeaturePolicySpec featurePolicy(String policyDirectives) {
return new FeaturePolicySpec(policyDirectives);
}
/**
* Configures {@code Permissions-Policy} response header.
* @return the {@link PermissionsPolicySpec} to configure
*/
public PermissionsPolicySpec permissionsPolicy() {
return new PermissionsPolicySpec();
}
/**
* Configures {@code Permissions-Policy} response header.
* @param permissionsPolicyCustomizer the {@link Customizer} to provide more
* options for the {@link PermissionsPolicySpec}
* @return the {@link HeaderSpec} to customize
*/
public HeaderSpec permissionsPolicy(Customizer<PermissionsPolicySpec> permissionsPolicyCustomizer) {
permissionsPolicyCustomizer.customize(new PermissionsPolicySpec());
return this;
}
/**
* Configures {@code Referrer-Policy} response header.
* @param referrerPolicy the policy to use
* @return the {@link ReferrerPolicySpec} to configure
*/
public ReferrerPolicySpec referrerPolicy(ReferrerPolicy referrerPolicy) {
return new ReferrerPolicySpec(referrerPolicy);
}
/**
* Configures {@code Referrer-Policy} response header.
* @return the {@link ReferrerPolicySpec} to configure
*/
public ReferrerPolicySpec referrerPolicy() {
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
*/
public HeaderSpec referrerPolicy(Customizer<ReferrerPolicySpec> referrerPolicyCustomizer) {
referrerPolicyCustomizer.customize(new ReferrerPolicySpec());
return this;
}
/**
* Configures the <a href=
* "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy">
* Cross-Origin-Opener-Policy</a> header.
* @return the {@link CrossOriginOpenerPolicySpec} to configure
* @since 5.7
* @see CrossOriginOpenerPolicyServerHttpHeadersWriter
*/
public CrossOriginOpenerPolicySpec crossOriginOpenerPolicy() {
return new CrossOriginOpenerPolicySpec();
}
/**
* Configures the <a href=
* "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy">
* Cross-Origin-Opener-Policy</a> header.
* @return the {@link HeaderSpec} to customize
* @since 5.7
* @see CrossOriginOpenerPolicyServerHttpHeadersWriter
*/
public HeaderSpec crossOriginOpenerPolicy(
Customizer<CrossOriginOpenerPolicySpec> crossOriginOpenerPolicyCustomizer) {
crossOriginOpenerPolicyCustomizer.customize(new CrossOriginOpenerPolicySpec());
return this;
}
/**
* Configures the <a href=
* "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy">
* Cross-Origin-Embedder-Policy</a> header.
* @return the {@link CrossOriginEmbedderPolicySpec} to configure
* @since 5.7
* @see CrossOriginEmbedderPolicyServerHttpHeadersWriter
*/
public CrossOriginEmbedderPolicySpec crossOriginEmbedderPolicy() {
return new CrossOriginEmbedderPolicySpec();
}
/**
* Configures the <a href=
* "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy">
* Cross-Origin-Embedder-Policy</a> header.
* @return the {@link HeaderSpec} to customize
* @since 5.7
* @see CrossOriginEmbedderPolicyServerHttpHeadersWriter
*/
public HeaderSpec crossOriginEmbedderPolicy(
Customizer<CrossOriginEmbedderPolicySpec> crossOriginEmbedderPolicyCustomizer) {
crossOriginEmbedderPolicyCustomizer.customize(new CrossOriginEmbedderPolicySpec());
return this;
}
/**
* Configures the <a href=
* "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy">
* Cross-Origin-Resource-Policy</a> header.
* @return the {@link CrossOriginResourcePolicySpec} to configure
* @since 5.7
* @see CrossOriginResourcePolicyServerHttpHeadersWriter
*/
public CrossOriginResourcePolicySpec crossOriginResourcePolicy() {
return new CrossOriginResourcePolicySpec();
}
/**
* Configures the <a href=
* "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy">
* Cross-Origin-Resource-Policy</a> header.
* @return the {@link HeaderSpec} to customize
* @since 5.7
* @see CrossOriginResourcePolicyServerHttpHeadersWriter
*/
public HeaderSpec crossOriginResourcePolicy(
Customizer<CrossOriginResourcePolicySpec> crossOriginResourcePolicyCustomizer) {
crossOriginResourcePolicyCustomizer.customize(new CrossOriginResourcePolicySpec());
return this;
}
/**
* Configures cache control headers
*
* @see #cache()
*/
public final class CacheSpec {
private CacheSpec() {
}
/**
* Disables cache control response headers
* @return the {@link HeaderSpec} to configure
*/
public HeaderSpec disable() {
HeaderSpec.this.writers.remove(HeaderSpec.this.cacheControl);
return HeaderSpec.this;
}
}
/**
* The content type headers
*
* @see #contentTypeOptions()
*/
public final class ContentTypeOptionsSpec {
private ContentTypeOptionsSpec() {
}
/**
* Disables the content type options response header
* @return the {@link HeaderSpec} to configure
*/
public HeaderSpec disable() {
HeaderSpec.this.writers.remove(HeaderSpec.this.contentTypeOptions);
return HeaderSpec.this;
}
}
/**
* Configures frame options response header
*
* @see #frameOptions()
*/
public final class FrameOptionsSpec {
private FrameOptionsSpec() {
}
/**
* The mode to configure. Default is
* {@link org.springframework.security.web.server.header.XFrameOptionsServerHttpHeadersWriter.Mode#DENY}
* @param mode the mode to use
* @return the {@link HeaderSpec} to configure
*/
public HeaderSpec mode(XFrameOptionsServerHttpHeadersWriter.Mode mode) {
HeaderSpec.this.frameOptions.setMode(mode);
return and();
}
/**
* Allows method chaining to continue configuring the
* {@link ServerHttpSecurity}
* @return the {@link HeaderSpec} to continue configuring
*/
private HeaderSpec and() {
return HeaderSpec.this;
}
/**
* Disables frame options response header
* @return the {@link HeaderSpec} to continue configuring
*/
public HeaderSpec disable() {
HeaderSpec.this.writers.remove(HeaderSpec.this.frameOptions);
return and();
}
}
/**
* Configures Strict Transport Security response header
*
* @see #hsts()
*/
public final class HstsSpec {
private HstsSpec() {
}
/**
* Configures the max age. Default is one year.
* @param maxAge the max age
* @return the {@link HstsSpec} to continue configuring
*/
public HstsSpec maxAge(Duration maxAge) {
HeaderSpec.this.hsts.setMaxAge(maxAge);
return this;
}
/**
* Configures if subdomains should be included. Default is true
* @param includeSubDomains if subdomains should be included
* @return the {@link HstsSpec} to continue configuring
*/
public HstsSpec includeSubdomains(boolean includeSubDomains) {
HeaderSpec.this.hsts.setIncludeSubDomains(includeSubDomains);
return this;
}
/**
* <p>
* Configures if preload should be included. Default is false
* </p>
*
* <p>
* See <a href="https://hstspreload.org/">Website hstspreload.org</a> for
* additional details.
* </p>
* @param preload if subdomains should be included
* @return the {@link HstsSpec} to continue configuring
* @since 5.2.0
*/
public HstsSpec preload(boolean preload) {
HeaderSpec.this.hsts.setPreload(preload);
return this;
}
/**
* Allows method chaining to continue configuring the
* {@link ServerHttpSecurity}
* @return the {@link HeaderSpec} to continue configuring
*/
public HeaderSpec and() {
return HeaderSpec.this;
}
/**
* Disables strict transport security response header
* @return the {@link HeaderSpec} to continue configuring
*/
public HeaderSpec disable() {
HeaderSpec.this.writers.remove(HeaderSpec.this.hsts);
return HeaderSpec.this;
}
}
/**
* Configures x-xss-protection response header
*
* @see #xssProtection()
*/
public final class XssProtectionSpec {
private XssProtectionSpec() {
}
/**
* Disables the x-xss-protection response header
* @return the {@link HeaderSpec} to continue configuring
*/
public HeaderSpec disable() {
HeaderSpec.this.writers.remove(HeaderSpec.this.xss);
return HeaderSpec.this;
}
}
/**
* Configures {@code Content-Security-Policy} response header.
*
* @since 5.1
* @see #contentSecurityPolicy(String)
*/
public final class ContentSecurityPolicySpec {
private static final String DEFAULT_SRC_SELF_POLICY = "default-src 'self'";
private ContentSecurityPolicySpec() {
HeaderSpec.this.contentSecurityPolicy.setPolicyDirectives(DEFAULT_SRC_SELF_POLICY);
}
/**
* Whether to include the {@code Content-Security-Policy-Report-Only} header
* in the response. Otherwise, defaults to the {@code Content-Security-Policy}
* header.
* @param reportOnly whether to only report policy violations
* @return the {@link HeaderSpec} to continue configuring
*/
public HeaderSpec reportOnly(boolean reportOnly) {
HeaderSpec.this.contentSecurityPolicy.setReportOnly(reportOnly);
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
* {@link ServerHttpSecurity}.
* @return the {@link HeaderSpec} to continue configuring
*/
public HeaderSpec and() {
return HeaderSpec.this;
}
private ContentSecurityPolicySpec(String policyDirectives) {
HeaderSpec.this.contentSecurityPolicy.setPolicyDirectives(policyDirectives);
}
}
/**
* Configures {@code Feature-Policy} response header.
*
* @since 5.1
* @see #featurePolicy(String)
*/
public final class FeaturePolicySpec {
private FeaturePolicySpec(String policyDirectives) {
HeaderSpec.this.featurePolicy.setPolicyDirectives(policyDirectives);
}
/**
* Allows method chaining to continue configuring the
* {@link ServerHttpSecurity}.
* @return the {@link HeaderSpec} to continue configuring
*/
public HeaderSpec and() {
return HeaderSpec.this;
}
}
/**
* Configures {@code Permissions-Policy} response header.
*
* @since 5.5
* @see #permissionsPolicy()
*/
public final class PermissionsPolicySpec {
private PermissionsPolicySpec() {
}
/**
* Sets the policy to be used in the response header.
* @param policy a permissions policy
* @return the {@link PermissionsPolicySpec} to continue configuring
*/
public PermissionsPolicySpec policy(String policy) {
HeaderSpec.this.permissionsPolicy.setPolicy(policy);
return this;
}
/**
* Allows method chaining to continue configuring the
* {@link ServerHttpSecurity}.
* @return the {@link HeaderSpec} to continue configuring
*/
public HeaderSpec and() {
return HeaderSpec.this;
}
}
/**
* Configures {@code Referrer-Policy} response header.
*
* @since 5.1
* @see #referrerPolicy()
* @see #referrerPolicy(ReferrerPolicy)
*/
public final class ReferrerPolicySpec {
private ReferrerPolicySpec() {
}
private ReferrerPolicySpec(ReferrerPolicy referrerPolicy) {
HeaderSpec.this.referrerPolicy.setPolicy(referrerPolicy);
}
/**
* 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
* {@link ServerHttpSecurity}.
* @return the {@link HeaderSpec} to continue configuring
*/
public HeaderSpec and() {
return HeaderSpec.this;
}
}
/**
* Configures the Cross-Origin-Opener-Policy header
*
* @since 5.7
*/
public final class CrossOriginOpenerPolicySpec {
private CrossOriginOpenerPolicySpec() {
}
/**
* Sets the value to be used in the `Cross-Origin-Opener-Policy` header
* @param openerPolicy a opener policy
* @return the {@link CrossOriginOpenerPolicySpec} to continue configuring
*/
public CrossOriginOpenerPolicySpec policy(CrossOriginOpenerPolicy openerPolicy) {
HeaderSpec.this.crossOriginOpenerPolicy.setPolicy(openerPolicy);
return this;
}
/**
* Allows method chaining to continue configuring the
* {@link ServerHttpSecurity}.
* @return the {@link HeaderSpec} to continue configuring
*/
public HeaderSpec and() {
return HeaderSpec.this;
}
}
/**
* Configures the Cross-Origin-Embedder-Policy header
*
* @since 5.7
*/
public final class CrossOriginEmbedderPolicySpec {
private CrossOriginEmbedderPolicySpec() {
}
/**
* Sets the value to be used in the `Cross-Origin-Embedder-Policy` header
* @param embedderPolicy a opener policy
* @return the {@link CrossOriginEmbedderPolicySpec} to continue configuring
*/
public CrossOriginEmbedderPolicySpec policy(CrossOriginEmbedderPolicy embedderPolicy) {
HeaderSpec.this.crossOriginEmbedderPolicy.setPolicy(embedderPolicy);
return this;
}
/**
* Allows method chaining to continue configuring the
* {@link ServerHttpSecurity}.
* @return the {@link HeaderSpec} to continue configuring
*/
public HeaderSpec and() {
return HeaderSpec.this;
}
}
/**
* Configures the Cross-Origin-Resource-Policy header
*
* @since 5.7
*/
public final class CrossOriginResourcePolicySpec {
private CrossOriginResourcePolicySpec() {
}
/**
* Sets the value to be used in the `Cross-Origin-Resource-Policy` header
* @param resourcePolicy a opener policy
* @return the {@link CrossOriginResourcePolicySpec} to continue configuring
*/
public CrossOriginResourcePolicySpec policy(CrossOriginResourcePolicy resourcePolicy) {
HeaderSpec.this.crossOriginResourcePolicy.setPolicy(resourcePolicy);
return this;
}
/**
* Allows method chaining to continue configuring the
* {@link ServerHttpSecurity}.
* @return the {@link HeaderSpec} to continue configuring
*/
public HeaderSpec and() {
return HeaderSpec.this;
}
}
}
/**
* Configures log out
*
* @author Shazin Sadakath
* @since 5.0
* @see #logout()
*/
public final class LogoutSpec {
private LogoutWebFilter logoutWebFilter = new LogoutWebFilter();
private final SecurityContextServerLogoutHandler DEFAULT_LOGOUT_HANDLER = new SecurityContextServerLogoutHandler();
private List<ServerLogoutHandler> logoutHandlers = new ArrayList<>(Arrays.asList(this.DEFAULT_LOGOUT_HANDLER));
private LogoutSpec() {
}
/**
* Configures the logout handler. Default is
* {@code SecurityContextServerLogoutHandler}
* @param logoutHandler
* @return the {@link LogoutSpec} to configure
*/
public LogoutSpec logoutHandler(ServerLogoutHandler logoutHandler) {
Assert.notNull(logoutHandler, "logoutHandler cannot be null");
this.logoutHandlers.clear();
return addLogoutHandler(logoutHandler);
}
private LogoutSpec addLogoutHandler(ServerLogoutHandler logoutHandler) {
Assert.notNull(logoutHandler, "logoutHandler cannot be null");
this.logoutHandlers.add(logoutHandler);
return this;
}
/**
* Configures what URL a POST to will trigger a log out.
* @param logoutUrl the url to trigger a log out (i.e. "/signout" would mean a
* POST to "/signout" would trigger log out)
* @return the {@link LogoutSpec} to configure
*/
public LogoutSpec logoutUrl(String logoutUrl) {
Assert.notNull(logoutUrl, "logoutUrl must not be null");
ServerWebExchangeMatcher requiresLogout = ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST,
logoutUrl);
return requiresLogout(requiresLogout);
}
/**
* Configures when the log out will be triggered.
* @param requiresLogout the matcher to determine when log out is triggered
* @return the {@link LogoutSpec} to configure
*/
public LogoutSpec requiresLogout(ServerWebExchangeMatcher requiresLogout) {
this.logoutWebFilter.setRequiresLogoutMatcher(requiresLogout);
return this;
}
public LogoutSpec logoutSuccessHandler(ServerLogoutSuccessHandler handler) {
this.logoutWebFilter.setLogoutSuccessHandler(handler);
return this;
}
/**
* Allows method chaining to continue configuring the {@link ServerHttpSecurity}
* @return the {@link ServerHttpSecurity} to continue configuring
*/
public ServerHttpSecurity and() {
return ServerHttpSecurity.this;
}
/**
* Disables log out
* @return the {@link ServerHttpSecurity} to continue configuring
*/
public ServerHttpSecurity disable() {
ServerHttpSecurity.this.logout = null;
return and();
}
private ServerLogoutHandler createLogoutHandler() {
ServerSecurityContextRepository securityContextRepository = ServerHttpSecurity.this.securityContextRepository;
if (securityContextRepository != null) {
this.DEFAULT_LOGOUT_HANDLER.setSecurityContextRepository(securityContextRepository);
}
if (this.logoutHandlers.isEmpty()) {
return null;
}
if (this.logoutHandlers.size() == 1) {
return this.logoutHandlers.get(0);
}
return new DelegatingServerLogoutHandler(this.logoutHandlers);
}
protected void configure(ServerHttpSecurity http) {
ServerLogoutHandler logoutHandler = createLogoutHandler();
if (logoutHandler != null) {
this.logoutWebFilter.setLogoutHandler(logoutHandler);
}
http.addFilterAt(this.logoutWebFilter, SecurityWebFiltersOrder.LOGOUT);
}
}
private static class OrderedWebFilter implements WebFilter, Ordered {
private final WebFilter webFilter;
private final int order;
OrderedWebFilter(WebFilter webFilter, int order) {
this.webFilter = webFilter;
this.order = order;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return this.webFilter.filter(exchange, chain);
}
@Override
public int getOrder() {
return this.order;
}
@Override
public String toString() {
return "OrderedWebFilter{" + "webFilter=" + this.webFilter + ", order=" + this.order + '}';
}
}
/**
* Workaround https://jira.spring.io/projects/SPR/issues/SPR-17213
*/
static class ServerWebExchangeReactorContextWebFilter implements WebFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return chain.filter(exchange).contextWrite(Context.of(ServerWebExchange.class, exchange));
}
}
/**
* Configures CORS support within Spring Security. This ensures that the
* {@link CorsWebFilter} is place in the correct order.
*/
public final class CorsSpec {
private CorsWebFilter corsFilter;
private CorsSpec() {
}
/**
* Configures the {@link CorsConfigurationSource} to be used
* @param source the source to use
* @return the {@link CorsSpec} for additional configuration
*/
public CorsSpec configurationSource(CorsConfigurationSource source) {
this.corsFilter = new CorsWebFilter(source);
return this;
}
/**
* Disables CORS support within Spring Security.
* @return the {@link ServerHttpSecurity} to continue configuring
*/
public ServerHttpSecurity disable() {
ServerHttpSecurity.this.cors = null;
return ServerHttpSecurity.this;
}
/**
* Allows method chaining to continue configuring the {@link ServerHttpSecurity}
* @return the {@link ServerHttpSecurity} to continue configuring
*/
public ServerHttpSecurity and() {
return ServerHttpSecurity.this;
}
protected void configure(ServerHttpSecurity http) {
CorsWebFilter corsFilter = getCorsFilter();
if (corsFilter != null) {
http.addFilterAt(this.corsFilter, SecurityWebFiltersOrder.CORS);
}
}
private CorsWebFilter getCorsFilter() {
if (this.corsFilter != null) {
return this.corsFilter;
}
CorsConfigurationSource source = getBeanOrNull(CorsConfigurationSource.class);
if (source == null) {
return null;
}
CorsProcessor processor = getBeanOrNull(CorsProcessor.class);
if (processor == null) {
processor = new DefaultCorsProcessor();
}
this.corsFilter = new CorsWebFilter(source, processor);
return this.corsFilter;
}
}
/**
* Configures X509 authentication
*
* @author Alexey Nesterov
* @since 5.2
* @see #x509()
*/
public final class X509Spec {
private X509PrincipalExtractor principalExtractor;
private ReactiveAuthenticationManager authenticationManager;
private X509Spec() {
}
public X509Spec principalExtractor(X509PrincipalExtractor principalExtractor) {
this.principalExtractor = principalExtractor;
return this;
}
public X509Spec authenticationManager(ReactiveAuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
return this;
}
public ServerHttpSecurity and() {
return ServerHttpSecurity.this;
}
protected void configure(ServerHttpSecurity http) {
ReactiveAuthenticationManager authenticationManager = getAuthenticationManager();
X509PrincipalExtractor principalExtractor = getPrincipalExtractor();
AuthenticationWebFilter filter = new AuthenticationWebFilter(authenticationManager);
filter.setServerAuthenticationConverter(new ServerX509AuthenticationConverter(principalExtractor));
http.addFilterAt(filter, SecurityWebFiltersOrder.AUTHENTICATION);
}
private X509PrincipalExtractor getPrincipalExtractor() {
if (this.principalExtractor != null) {
return this.principalExtractor;
}
return new SubjectDnX509PrincipalExtractor();
}
private ReactiveAuthenticationManager getAuthenticationManager() {
if (this.authenticationManager != null) {
return this.authenticationManager;
}
ReactiveUserDetailsService userDetailsService = getBean(ReactiveUserDetailsService.class);
return new ReactivePreAuthenticatedAuthenticationManager(userDetailsService);
}
}
public final class OAuth2LoginSpec {
private ReactiveClientRegistrationRepository clientRegistrationRepository;
private ServerOAuth2AuthorizedClientRepository authorizedClientRepository;
private ServerAuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository;
private ReactiveAuthenticationManager authenticationManager;
private ServerSecurityContextRepository securityContextRepository;
private ServerAuthenticationConverter authenticationConverter;
private ServerOAuth2AuthorizationRequestResolver authorizationRequestResolver;
private ServerRedirectStrategy authorizationRedirectStrategy;
private ServerWebExchangeMatcher authenticationMatcher;
private ServerAuthenticationSuccessHandler authenticationSuccessHandler;
private ServerAuthenticationFailureHandler authenticationFailureHandler;
private OAuth2LoginSpec() {
}
/**
* Configures the {@link ReactiveAuthenticationManager} to use. The default is
* {@link OAuth2AuthorizationCodeReactiveAuthenticationManager}
* @param authenticationManager the manager to use
* @return the {@link OAuth2LoginSpec} to customize
*/
public OAuth2LoginSpec authenticationManager(ReactiveAuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
return this;
}
/**
* The {@link ServerSecurityContextRepository} used to save the
* {@code Authentication}. Defaults to
* {@link WebSessionServerSecurityContextRepository}.
* @param securityContextRepository the repository to use
* @return the {@link OAuth2LoginSpec} to continue configuring
* @since 5.2
*/
public OAuth2LoginSpec securityContextRepository(ServerSecurityContextRepository securityContextRepository) {
this.securityContextRepository = securityContextRepository;
return this;
}
/**
* The {@link ServerAuthenticationSuccessHandler} used after authentication
* success. Defaults to {@link RedirectServerAuthenticationSuccessHandler}
* redirecting to "/".
* @param authenticationSuccessHandler the success handler to use
* @return the {@link OAuth2LoginSpec} to customize
* @since 5.2
*/
public OAuth2LoginSpec authenticationSuccessHandler(
ServerAuthenticationSuccessHandler authenticationSuccessHandler) {
Assert.notNull(authenticationSuccessHandler, "authenticationSuccessHandler cannot be null");
this.authenticationSuccessHandler = authenticationSuccessHandler;
return this;
}
/**
* The {@link ServerAuthenticationFailureHandler} used after authentication
* failure. Defaults to {@link RedirectServerAuthenticationFailureHandler}
* redirecting to "/login?error".
* @param authenticationFailureHandler the failure handler to use
* @return the {@link OAuth2LoginSpec} to customize
* @since 5.2
*/
public OAuth2LoginSpec authenticationFailureHandler(
ServerAuthenticationFailureHandler authenticationFailureHandler) {
Assert.notNull(authenticationFailureHandler, "authenticationFailureHandler cannot be null");
this.authenticationFailureHandler = authenticationFailureHandler;
return this;
}
/**
* Gets the {@link ReactiveAuthenticationManager} to use. First tries an
* explicitly configured manager, and defaults to
* {@link OAuth2AuthorizationCodeReactiveAuthenticationManager}
* @return the {@link ReactiveAuthenticationManager} to use
*/
private ReactiveAuthenticationManager getAuthenticationManager() {
if (this.authenticationManager == null) {
this.authenticationManager = createDefault();
}
return this.authenticationManager;
}
private ReactiveAuthenticationManager createDefault() {
ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> client = getAccessTokenResponseClient();
OAuth2LoginReactiveAuthenticationManager oauth2Manager = new OAuth2LoginReactiveAuthenticationManager(
client, getOauth2UserService());
GrantedAuthoritiesMapper authoritiesMapper = getBeanOrNull(GrantedAuthoritiesMapper.class);
if (authoritiesMapper != null) {
oauth2Manager.setAuthoritiesMapper(authoritiesMapper);
}
boolean oidcAuthenticationProviderEnabled = ClassUtils
.isPresent("org.springframework.security.oauth2.jwt.JwtDecoder", this.getClass().getClassLoader());
if (!oidcAuthenticationProviderEnabled) {
return oauth2Manager;
}
OidcAuthorizationCodeReactiveAuthenticationManager oidc = new OidcAuthorizationCodeReactiveAuthenticationManager(
client, getOidcUserService());
ResolvableType type = ResolvableType.forClassWithGenerics(ReactiveJwtDecoderFactory.class,
ClientRegistration.class);
ReactiveJwtDecoderFactory<ClientRegistration> jwtDecoderFactory = getBeanOrNull(type);
if (jwtDecoderFactory != null) {
oidc.setJwtDecoderFactory(jwtDecoderFactory);
}
if (authoritiesMapper != null) {
oidc.setAuthoritiesMapper(authoritiesMapper);
}
return new DelegatingReactiveAuthenticationManager(oidc, oauth2Manager);
}
/**
* Sets the converter to use
* @param authenticationConverter the converter to use
* @return the {@link OAuth2LoginSpec} to customize
*/
public OAuth2LoginSpec authenticationConverter(ServerAuthenticationConverter authenticationConverter) {
this.authenticationConverter = authenticationConverter;
return this;
}
private ServerAuthenticationConverter getAuthenticationConverter(
ReactiveClientRegistrationRepository clientRegistrationRepository) {
if (this.authenticationConverter != null) {
return this.authenticationConverter;
}
ServerOAuth2AuthorizationCodeAuthenticationTokenConverter delegate = new ServerOAuth2AuthorizationCodeAuthenticationTokenConverter(
clientRegistrationRepository);
delegate.setAuthorizationRequestRepository(getAuthorizationRequestRepository());
ServerAuthenticationConverter authenticationConverter = (exchange) -> delegate.convert(exchange).onErrorMap(
OAuth2AuthorizationException.class,
(e) -> new OAuth2AuthenticationException(e.getError(), e.getError().toString()));
this.authenticationConverter = authenticationConverter;
return authenticationConverter;
}
public OAuth2LoginSpec clientRegistrationRepository(
ReactiveClientRegistrationRepository clientRegistrationRepository) {
this.clientRegistrationRepository = clientRegistrationRepository;
return this;
}
public OAuth2LoginSpec authorizedClientService(ReactiveOAuth2AuthorizedClientService authorizedClientService) {
this.authorizedClientRepository = new AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository(
authorizedClientService);
return this;
}
public OAuth2LoginSpec authorizedClientRepository(
ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
this.authorizedClientRepository = authorizedClientRepository;
return this;
}
/**
* Sets the repository to use for storing {@link OAuth2AuthorizationRequest}'s.
* @param authorizationRequestRepository the repository to use for storing
* {@link OAuth2AuthorizationRequest}'s
* @return the {@link OAuth2LoginSpec} for further configuration
* @since 5.2
*/
public OAuth2LoginSpec authorizationRequestRepository(
ServerAuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository) {
this.authorizationRequestRepository = authorizationRequestRepository;
return this;
}
/**
* Sets the resolver used for resolving {@link OAuth2AuthorizationRequest}'s.
* @param authorizationRequestResolver the resolver used for resolving
* {@link OAuth2AuthorizationRequest}'s
* @return the {@link OAuth2LoginSpec} for further configuration
* @since 5.2
*/
public OAuth2LoginSpec authorizationRequestResolver(
ServerOAuth2AuthorizationRequestResolver authorizationRequestResolver) {
this.authorizationRequestResolver = authorizationRequestResolver;
return this;
}
/**
* Sets the redirect strategy for Authorization Endpoint redirect URI.
* @param authorizationRedirectStrategy the redirect strategy
* @return the {@link OAuth2LoginSpec} for further configuration
*/
public OAuth2LoginSpec authorizationRedirectStrategy(ServerRedirectStrategy authorizationRedirectStrategy) {
this.authorizationRedirectStrategy = authorizationRedirectStrategy;
return this;
}
/**
* Sets the {@link ServerWebExchangeMatcher matcher} used for determining if the
* request is an authentication request.
* @param authenticationMatcher the {@link ServerWebExchangeMatcher matcher} used
* for determining if the request is an authentication request
* @return the {@link OAuth2LoginSpec} for further configuration
* @since 5.2
*/
public OAuth2LoginSpec authenticationMatcher(ServerWebExchangeMatcher authenticationMatcher) {
this.authenticationMatcher = authenticationMatcher;
return this;
}
private ServerWebExchangeMatcher getAuthenticationMatcher() {
if (this.authenticationMatcher == null) {
this.authenticationMatcher = createAttemptAuthenticationRequestMatcher();
}
return this.authenticationMatcher;
}
/**
* Allows method chaining to continue configuring the {@link ServerHttpSecurity}
* @return the {@link ServerHttpSecurity} to continue configuring
*/
public ServerHttpSecurity and() {
return ServerHttpSecurity.this;
}
protected void configure(ServerHttpSecurity http) {
ReactiveClientRegistrationRepository clientRegistrationRepository = getClientRegistrationRepository();
ServerOAuth2AuthorizedClientRepository authorizedClientRepository = getAuthorizedClientRepository();
OAuth2AuthorizationRequestRedirectWebFilter oauthRedirectFilter = getRedirectWebFilter();
ServerAuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository = getAuthorizationRequestRepository();
oauthRedirectFilter.setAuthorizationRequestRepository(authorizationRequestRepository);
oauthRedirectFilter.setAuthorizationRedirectStrategy(getAuthorizationRedirectStrategy());
oauthRedirectFilter.setRequestCache(http.requestCache.requestCache);
ReactiveAuthenticationManager manager = getAuthenticationManager();
AuthenticationWebFilter authenticationFilter = new OAuth2LoginAuthenticationWebFilter(manager,
authorizedClientRepository);
authenticationFilter.setRequiresAuthenticationMatcher(getAuthenticationMatcher());
authenticationFilter
.setServerAuthenticationConverter(getAuthenticationConverter(clientRegistrationRepository));
authenticationFilter.setAuthenticationSuccessHandler(getAuthenticationSuccessHandler(http));
authenticationFilter.setAuthenticationFailureHandler(getAuthenticationFailureHandler());
authenticationFilter.setSecurityContextRepository(this.securityContextRepository);
setDefaultEntryPoints(http);
http.addFilterAt(oauthRedirectFilter, SecurityWebFiltersOrder.HTTP_BASIC);
http.addFilterAt(authenticationFilter, SecurityWebFiltersOrder.AUTHENTICATION);
}
private void setDefaultEntryPoints(ServerHttpSecurity http) {
String defaultLoginPage = "/login";
Map<String, String> urlToText = http.oauth2Login.getLinks();
String providerLoginPage = null;
if (urlToText.size() == 1) {
providerLoginPage = urlToText.keySet().iterator().next();
}
MediaTypeServerWebExchangeMatcher htmlMatcher = new MediaTypeServerWebExchangeMatcher(
MediaType.APPLICATION_XHTML_XML, new MediaType("image", "*"), MediaType.TEXT_HTML,
MediaType.TEXT_PLAIN);
htmlMatcher.setIgnoredMediaTypes(Collections.singleton(MediaType.ALL));
ServerWebExchangeMatcher xhrMatcher = (exchange) -> {
if (exchange.getRequest().getHeaders().getOrEmpty("X-Requested-With").contains("XMLHttpRequest")) {
return ServerWebExchangeMatcher.MatchResult.match();
}
return ServerWebExchangeMatcher.MatchResult.notMatch();
};
ServerWebExchangeMatcher notXhrMatcher = new NegatedServerWebExchangeMatcher(xhrMatcher);
ServerWebExchangeMatcher defaultEntryPointMatcher = new AndServerWebExchangeMatcher(notXhrMatcher,
htmlMatcher);
if (providerLoginPage != null) {
ServerWebExchangeMatcher loginPageMatcher = new PathPatternParserServerWebExchangeMatcher(
defaultLoginPage);
ServerWebExchangeMatcher faviconMatcher = new PathPatternParserServerWebExchangeMatcher("/favicon.ico");
ServerWebExchangeMatcher defaultLoginPageMatcher = new AndServerWebExchangeMatcher(
new OrServerWebExchangeMatcher(loginPageMatcher, faviconMatcher), defaultEntryPointMatcher);
ServerWebExchangeMatcher matcher = new AndServerWebExchangeMatcher(notXhrMatcher,
new NegatedServerWebExchangeMatcher(defaultLoginPageMatcher));
RedirectServerAuthenticationEntryPoint entryPoint = new RedirectServerAuthenticationEntryPoint(
providerLoginPage);
entryPoint.setRequestCache(http.requestCache.requestCache);
http.defaultEntryPoints.add(new DelegateEntry(matcher, entryPoint));
}
RedirectServerAuthenticationEntryPoint defaultEntryPoint = new RedirectServerAuthenticationEntryPoint(
defaultLoginPage);
defaultEntryPoint.setRequestCache(http.requestCache.requestCache);
http.defaultEntryPoints.add(new DelegateEntry(defaultEntryPointMatcher, defaultEntryPoint));
}
private ServerAuthenticationSuccessHandler getAuthenticationSuccessHandler(ServerHttpSecurity http) {
if (this.authenticationSuccessHandler == null) {
RedirectServerAuthenticationSuccessHandler handler = new RedirectServerAuthenticationSuccessHandler();
handler.setRequestCache(http.requestCache.requestCache);
this.authenticationSuccessHandler = handler;
}
return this.authenticationSuccessHandler;
}
private ServerAuthenticationFailureHandler getAuthenticationFailureHandler() {
if (this.authenticationFailureHandler == null) {
this.authenticationFailureHandler = new RedirectServerAuthenticationFailureHandler("/login?error");
}
return this.authenticationFailureHandler;
}
private ServerWebExchangeMatcher createAttemptAuthenticationRequestMatcher() {
return new PathPatternParserServerWebExchangeMatcher("/login/oauth2/code/{registrationId}");
}
private ReactiveOAuth2UserService<OidcUserRequest, OidcUser> getOidcUserService() {
ResolvableType type = ResolvableType.forClassWithGenerics(ReactiveOAuth2UserService.class,
OidcUserRequest.class, OidcUser.class);
ReactiveOAuth2UserService<OidcUserRequest, OidcUser> bean = getBeanOrNull(type);
if (bean != null) {
return bean;
}
return new OidcReactiveOAuth2UserService();
}
private ReactiveOAuth2UserService<OAuth2UserRequest, OAuth2User> getOauth2UserService() {
ResolvableType type = ResolvableType.forClassWithGenerics(ReactiveOAuth2UserService.class,
OAuth2UserRequest.class, OAuth2User.class);
ReactiveOAuth2UserService<OAuth2UserRequest, OAuth2User> bean = getBeanOrNull(type);
if (bean != null) {
return bean;
}
return new DefaultReactiveOAuth2UserService();
}
private Map<String, String> getLinks() {
Iterable<ClientRegistration> registrations = getBeanOrNull(
ResolvableType.forClassWithGenerics(Iterable.class, ClientRegistration.class));
if (registrations == null) {
return Collections.emptyMap();
}
Map<String, String> result = new HashMap<>();
registrations.iterator().forEachRemaining((r) -> {
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(r.getAuthorizationGrantType())) {
result.put("/oauth2/authorization/" + r.getRegistrationId(), r.getClientName());
}
});
return result;
}
private ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> getAccessTokenResponseClient() {
ResolvableType type = ResolvableType.forClassWithGenerics(ReactiveOAuth2AccessTokenResponseClient.class,
OAuth2AuthorizationCodeGrantRequest.class);
ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> bean = getBeanOrNull(type);
if (bean != null) {
return bean;
}
return new WebClientReactiveAuthorizationCodeTokenResponseClient();
}
private ReactiveClientRegistrationRepository getClientRegistrationRepository() {
if (this.clientRegistrationRepository == null) {
this.clientRegistrationRepository = getBeanOrNull(ReactiveClientRegistrationRepository.class);
}
return this.clientRegistrationRepository;
}
private OAuth2AuthorizationRequestRedirectWebFilter getRedirectWebFilter() {
OAuth2AuthorizationRequestRedirectWebFilter oauthRedirectFilter;
if (this.authorizationRequestResolver != null) {
return new OAuth2AuthorizationRequestRedirectWebFilter(this.authorizationRequestResolver);
}
return new OAuth2AuthorizationRequestRedirectWebFilter(getClientRegistrationRepository());
}
private ServerOAuth2AuthorizedClientRepository getAuthorizedClientRepository() {
ServerOAuth2AuthorizedClientRepository result = this.authorizedClientRepository;
if (result == null) {
result = getBeanOrNull(ServerOAuth2AuthorizedClientRepository.class);
}
if (result == null) {
ReactiveOAuth2AuthorizedClientService authorizedClientService = getAuthorizedClientService();
if (authorizedClientService != null) {
result = new AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository(authorizedClientService);
}
}
return result;
}
private ServerAuthorizationRequestRepository<OAuth2AuthorizationRequest> getAuthorizationRequestRepository() {
if (this.authorizationRequestRepository == null) {
this.authorizationRequestRepository = new WebSessionOAuth2ServerAuthorizationRequestRepository();
}
return this.authorizationRequestRepository;
}
private ServerRedirectStrategy getAuthorizationRedirectStrategy() {
if (this.authorizationRedirectStrategy == null) {
this.authorizationRedirectStrategy = new DefaultServerRedirectStrategy();
}
return this.authorizationRedirectStrategy;
}
private ReactiveOAuth2AuthorizedClientService getAuthorizedClientService() {
ReactiveOAuth2AuthorizedClientService bean = getBeanOrNull(ReactiveOAuth2AuthorizedClientService.class);
if (bean != null) {
return bean;
}
return new InMemoryReactiveOAuth2AuthorizedClientService(getClientRegistrationRepository());
}
}
public final class OAuth2ClientSpec {
private ReactiveClientRegistrationRepository clientRegistrationRepository;
private ServerAuthenticationConverter authenticationConverter;
private ServerOAuth2AuthorizedClientRepository authorizedClientRepository;
private ReactiveAuthenticationManager authenticationManager;
private ServerAuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository;
private ServerRedirectStrategy authorizationRedirectStrategy;
private OAuth2ClientSpec() {
}
/**
* Sets the converter to use
* @param authenticationConverter the converter to use
* @return the {@link OAuth2ClientSpec} to customize
*/
public OAuth2ClientSpec authenticationConverter(ServerAuthenticationConverter authenticationConverter) {
this.authenticationConverter = authenticationConverter;
return this;
}
private ServerAuthenticationConverter getAuthenticationConverter() {
if (this.authenticationConverter == null) {
ServerOAuth2AuthorizationCodeAuthenticationTokenConverter authenticationConverter = new ServerOAuth2AuthorizationCodeAuthenticationTokenConverter(
getClientRegistrationRepository());
authenticationConverter.setAuthorizationRequestRepository(getAuthorizationRequestRepository());
this.authenticationConverter = authenticationConverter;
}
return this.authenticationConverter;
}
/**
* Configures the {@link ReactiveAuthenticationManager} to use. The default is
* {@link OAuth2AuthorizationCodeReactiveAuthenticationManager}
* @param authenticationManager the manager to use
* @return the {@link OAuth2ClientSpec} to customize
*/
public OAuth2ClientSpec authenticationManager(ReactiveAuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
return this;
}
/**
* Gets the {@link ReactiveAuthenticationManager} to use. First tries an
* explicitly configured manager, and defaults to
* {@link OAuth2AuthorizationCodeReactiveAuthenticationManager}
* @return the {@link ReactiveAuthenticationManager} to use
*/
private ReactiveAuthenticationManager getAuthenticationManager() {
if (this.authenticationManager == null) {
this.authenticationManager = new OAuth2AuthorizationCodeReactiveAuthenticationManager(
new WebClientReactiveAuthorizationCodeTokenResponseClient());
}
return this.authenticationManager;
}
/**
* Configures the {@link ReactiveClientRegistrationRepository}. Default is to look
* the value up as a Bean.
* @param clientRegistrationRepository the repository to use
* @return the {@link OAuth2ClientSpec} to customize
*/
public OAuth2ClientSpec clientRegistrationRepository(
ReactiveClientRegistrationRepository clientRegistrationRepository) {
this.clientRegistrationRepository = clientRegistrationRepository;
return this;
}
/**
* Configures the {@link ReactiveClientRegistrationRepository}. Default is to look
* the value up as a Bean.
* @param authorizedClientRepository the repository to use
* @return the {@link OAuth2ClientSpec} to customize
*/
public OAuth2ClientSpec authorizedClientRepository(
ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
this.authorizedClientRepository = authorizedClientRepository;
return this;
}
/**
* Sets the repository to use for storing {@link OAuth2AuthorizationRequest}'s.
* @param authorizationRequestRepository the repository to use for storing
* {@link OAuth2AuthorizationRequest}'s
* @return the {@link OAuth2ClientSpec} to customize
* @since 5.2
*/
public OAuth2ClientSpec authorizationRequestRepository(
ServerAuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository) {
this.authorizationRequestRepository = authorizationRequestRepository;
return this;
}
private ServerAuthorizationRequestRepository<OAuth2AuthorizationRequest> getAuthorizationRequestRepository() {
if (this.authorizationRequestRepository == null) {
this.authorizationRequestRepository = new WebSessionOAuth2ServerAuthorizationRequestRepository();
}
return this.authorizationRequestRepository;
}
/**
* Sets the redirect strategy for Authorization Endpoint redirect URI.
* @param authorizationRedirectStrategy the redirect strategy
* @return the {@link OAuth2ClientSpec} for further configuration
*/
public OAuth2ClientSpec authorizationRedirectStrategy(ServerRedirectStrategy authorizationRedirectStrategy) {
this.authorizationRedirectStrategy = authorizationRedirectStrategy;
return this;
}
private ServerRedirectStrategy getAuthorizationRedirectStrategy() {
if (this.authorizationRedirectStrategy == null) {
this.authorizationRedirectStrategy = new DefaultServerRedirectStrategy();
}
return this.authorizationRedirectStrategy;
}
/**
* Allows method chaining to continue configuring the {@link ServerHttpSecurity}
* @return the {@link ServerHttpSecurity} to continue configuring
*/
public ServerHttpSecurity and() {
return ServerHttpSecurity.this;
}
protected void configure(ServerHttpSecurity http) {
ReactiveClientRegistrationRepository clientRegistrationRepository = getClientRegistrationRepository();
ServerOAuth2AuthorizedClientRepository authorizedClientRepository = getAuthorizedClientRepository();
ServerAuthenticationConverter authenticationConverter = getAuthenticationConverter();
ReactiveAuthenticationManager authenticationManager = getAuthenticationManager();
OAuth2AuthorizationCodeGrantWebFilter codeGrantWebFilter = new OAuth2AuthorizationCodeGrantWebFilter(
authenticationManager, authenticationConverter, authorizedClientRepository);
codeGrantWebFilter.setAuthorizationRequestRepository(getAuthorizationRequestRepository());
if (http.requestCache != null) {
codeGrantWebFilter.setRequestCache(http.requestCache.requestCache);
}
OAuth2AuthorizationRequestRedirectWebFilter oauthRedirectFilter = new OAuth2AuthorizationRequestRedirectWebFilter(
clientRegistrationRepository);
oauthRedirectFilter.setAuthorizationRequestRepository(getAuthorizationRequestRepository());
oauthRedirectFilter.setAuthorizationRedirectStrategy(getAuthorizationRedirectStrategy());
if (http.requestCache != null) {
oauthRedirectFilter.setRequestCache(http.requestCache.requestCache);
}
http.addFilterAt(codeGrantWebFilter, SecurityWebFiltersOrder.OAUTH2_AUTHORIZATION_CODE);
http.addFilterAt(oauthRedirectFilter, SecurityWebFiltersOrder.HTTP_BASIC);
}
private ReactiveClientRegistrationRepository getClientRegistrationRepository() {
if (this.clientRegistrationRepository != null) {
return this.clientRegistrationRepository;
}
return getBeanOrNull(ReactiveClientRegistrationRepository.class);
}
private ServerOAuth2AuthorizedClientRepository getAuthorizedClientRepository() {
if (this.authorizedClientRepository != null) {
return this.authorizedClientRepository;
}
ServerOAuth2AuthorizedClientRepository result = getBeanOrNull(ServerOAuth2AuthorizedClientRepository.class);
if (result != null) {
return result;
}
ReactiveOAuth2AuthorizedClientService authorizedClientService = getAuthorizedClientService();
if (authorizedClientService != null) {
return new AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository(authorizedClientService);
}
return null;
}
private ReactiveOAuth2AuthorizedClientService getAuthorizedClientService() {
ReactiveOAuth2AuthorizedClientService bean = getBeanOrNull(ReactiveOAuth2AuthorizedClientService.class);
if (bean != null) {
return bean;
}
return new InMemoryReactiveOAuth2AuthorizedClientService(getClientRegistrationRepository());
}
}
/**
* Configures OAuth2 Resource Server Support
*/
public class OAuth2ResourceServerSpec {
private ServerAuthenticationEntryPoint entryPoint = new BearerTokenServerAuthenticationEntryPoint();
private ServerAccessDeniedHandler accessDeniedHandler = new BearerTokenServerAccessDeniedHandler();
private ServerAuthenticationConverter bearerTokenConverter = new ServerBearerTokenAuthenticationConverter();
private AuthenticationConverterServerWebExchangeMatcher authenticationConverterServerWebExchangeMatcher;
private JwtSpec jwt;
private OpaqueTokenSpec opaqueToken;
private ReactiveAuthenticationManagerResolver<ServerWebExchange> authenticationManagerResolver;
/**
* Configures the {@link ServerAccessDeniedHandler} to use for requests
* authenticating with
* <a href="https://tools.ietf.org/html/rfc6750#section-1.2" target=
* "_blank">Bearer Token</a>s. requests.
* @param accessDeniedHandler the {@link ServerAccessDeniedHandler} to use
* @return the {@link OAuth2ResourceServerSpec} for additional configuration
* @since 5.2
*/
public OAuth2ResourceServerSpec accessDeniedHandler(ServerAccessDeniedHandler accessDeniedHandler) {
Assert.notNull(accessDeniedHandler, "accessDeniedHandler cannot be null");
this.accessDeniedHandler = accessDeniedHandler;
return this;
}
/**
* Configures the {@link ServerAuthenticationEntryPoint} to use for requests
* authenticating with
* <a href="https://tools.ietf.org/html/rfc6750#section-1.2" target=
* "_blank">Bearer Token</a>s.
* @param entryPoint the {@link ServerAuthenticationEntryPoint} to use
* @return the {@link OAuth2ResourceServerSpec} for additional configuration
* @since 5.2
*/
public OAuth2ResourceServerSpec authenticationEntryPoint(ServerAuthenticationEntryPoint entryPoint) {
Assert.notNull(entryPoint, "entryPoint cannot be null");
this.entryPoint = entryPoint;
return this;
}
/**
* Configures the {@link ServerAuthenticationConverter} to use for requests
* authenticating with
* <a href="https://tools.ietf.org/html/rfc6750#section-1.2" target=
* "_blank">Bearer Token</a>s.
* @param bearerTokenConverter The {@link ServerAuthenticationConverter} to use
* @return The {@link OAuth2ResourceServerSpec} for additional configuration
* @since 5.2
*/
public OAuth2ResourceServerSpec bearerTokenConverter(ServerAuthenticationConverter bearerTokenConverter) {
Assert.notNull(bearerTokenConverter, "bearerTokenConverter cannot be null");
this.bearerTokenConverter = bearerTokenConverter;
return this;
}
/**
* Configures the {@link ReactiveAuthenticationManagerResolver}
* @param authenticationManagerResolver the
* {@link ReactiveAuthenticationManagerResolver}
* @return the {@link OAuth2ResourceServerSpec} for additional configuration
* @since 5.3
*/
public OAuth2ResourceServerSpec authenticationManagerResolver(
ReactiveAuthenticationManagerResolver<ServerWebExchange> authenticationManagerResolver) {
Assert.notNull(authenticationManagerResolver, "authenticationManagerResolver cannot be null");
this.authenticationManagerResolver = authenticationManagerResolver;
return this;
}
/**
* Enables JWT Resource Server support.
* @return the {@link JwtSpec} for additional configuration
*/
public JwtSpec jwt() {
if (this.jwt == null) {
this.jwt = new JwtSpec();
}
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
*/
public OAuth2ResourceServerSpec jwt(Customizer<JwtSpec> jwtCustomizer) {
if (this.jwt == null) {
this.jwt = new JwtSpec();
}
jwtCustomizer.customize(this.jwt);
return this;
}
/**
* Enables Opaque Token Resource Server support.
* @return the {@link OpaqueTokenSpec} for additional configuration
*/
public OpaqueTokenSpec opaqueToken() {
if (this.opaqueToken == null) {
this.opaqueToken = new OpaqueTokenSpec();
}
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
*/
public OAuth2ResourceServerSpec opaqueToken(Customizer<OpaqueTokenSpec> opaqueTokenCustomizer) {
if (this.opaqueToken == null) {
this.opaqueToken = new OpaqueTokenSpec();
}
opaqueTokenCustomizer.customize(this.opaqueToken);
return this;
}
protected void configure(ServerHttpSecurity http) {
this.authenticationConverterServerWebExchangeMatcher = new AuthenticationConverterServerWebExchangeMatcher(
this.bearerTokenConverter);
registerDefaultAccessDeniedHandler(http);
registerDefaultAuthenticationEntryPoint(http);
registerDefaultCsrfOverride(http);
validateConfiguration();
if (this.authenticationManagerResolver != null) {
AuthenticationWebFilter oauth2 = new AuthenticationWebFilter(this.authenticationManagerResolver);
oauth2.setServerAuthenticationConverter(this.bearerTokenConverter);
oauth2.setAuthenticationFailureHandler(
new ServerAuthenticationEntryPointFailureHandler(this.entryPoint));
http.addFilterAt(oauth2, SecurityWebFiltersOrder.AUTHENTICATION);
}
else if (this.jwt != null) {
this.jwt.configure(http);
}
else if (this.opaqueToken != null) {
this.opaqueToken.configure(http);
}
}
private void validateConfiguration() {
if (this.authenticationManagerResolver == null) {
Assert.state(this.jwt != null || this.opaqueToken != null,
"Jwt and Opaque Token are the only supported formats for bearer tokens "
+ "in Spring Security and neither was found. Make sure to configure JWT "
+ "via http.oauth2ResourceServer().jwt() or Opaque Tokens via "
+ "http.oauth2ResourceServer().opaqueToken().");
Assert.state(this.jwt == null || this.opaqueToken == null,
"Spring Security only supports JWTs or Opaque Tokens, not both at the " + "same time.");
}
else {
Assert.state(this.jwt == null && this.opaqueToken == null,
"If an authenticationManagerResolver() is configured, then it takes "
+ "precedence over any jwt() or opaqueToken() configuration.");
}
}
private void registerDefaultAccessDeniedHandler(ServerHttpSecurity http) {
if (http.exceptionHandling != null) {
http.defaultAccessDeniedHandlers
.add(new ServerWebExchangeDelegatingServerAccessDeniedHandler.DelegateEntry(
this.authenticationConverterServerWebExchangeMatcher,
OAuth2ResourceServerSpec.this.accessDeniedHandler));
}
}
private void registerDefaultAuthenticationEntryPoint(ServerHttpSecurity http) {
if (http.exceptionHandling != null) {
http.defaultEntryPoints.add(new DelegateEntry(this.authenticationConverterServerWebExchangeMatcher,
OAuth2ResourceServerSpec.this.entryPoint));
}
}
private void registerDefaultCsrfOverride(ServerHttpSecurity http) {
if (http.csrf != null && !http.csrf.specifiedRequireCsrfProtectionMatcher) {
AndServerWebExchangeMatcher matcher = new AndServerWebExchangeMatcher(
CsrfWebFilter.DEFAULT_CSRF_MATCHER,
new NegatedServerWebExchangeMatcher(this.authenticationConverterServerWebExchangeMatcher));
http.csrf().requireCsrfProtectionMatcher(matcher);
}
}
public ServerHttpSecurity and() {
return ServerHttpSecurity.this;
}
/**
* Configures JWT Resource Server Support
*/
public class JwtSpec {
private ReactiveAuthenticationManager authenticationManager;
private ReactiveJwtDecoder jwtDecoder;
private Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> jwtAuthenticationConverter;
/**
* Configures the {@link ReactiveAuthenticationManager} to use
* @param authenticationManager the authentication manager to use
* @return the {@code JwtSpec} for additional configuration
*/
public JwtSpec authenticationManager(ReactiveAuthenticationManager authenticationManager) {
Assert.notNull(authenticationManager, "authenticationManager cannot be null");
this.authenticationManager = authenticationManager;
return this;
}
/**
* Configures the {@link Converter} to use for converting a {@link Jwt} into
* an {@link AbstractAuthenticationToken}.
* @param jwtAuthenticationConverter the converter to use
* @return the {@code JwtSpec} for additional configuration
* @since 5.1.1
*/
public JwtSpec jwtAuthenticationConverter(
Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> jwtAuthenticationConverter) {
Assert.notNull(jwtAuthenticationConverter, "jwtAuthenticationConverter cannot be null");
this.jwtAuthenticationConverter = jwtAuthenticationConverter;
return this;
}
/**
* Configures the {@link ReactiveJwtDecoder} to use
* @param jwtDecoder the decoder to use
* @return the {@code JwtSpec} for additional configuration
*/
public JwtSpec jwtDecoder(ReactiveJwtDecoder jwtDecoder) {
this.jwtDecoder = jwtDecoder;
return this;
}
/**
* Configures a {@link ReactiveJwtDecoder} that leverages the provided
* {@link RSAPublicKey}
* @param publicKey the public key to use.
* @return the {@code JwtSpec} for additional configuration
*/
public JwtSpec publicKey(RSAPublicKey publicKey) {
this.jwtDecoder = new NimbusReactiveJwtDecoder(publicKey);
return this;
}
/**
* Configures a {@link ReactiveJwtDecoder} using
* <a target="_blank" href="https://tools.ietf.org/html/rfc7517">JSON Web Key
* (JWK)</a> URL
* @param jwkSetUri the URL to use.
* @return the {@code JwtSpec} for additional configuration
*/
public JwtSpec jwkSetUri(String jwkSetUri) {
this.jwtDecoder = new NimbusReactiveJwtDecoder(jwkSetUri);
return this;
}
public OAuth2ResourceServerSpec and() {
return OAuth2ResourceServerSpec.this;
}
protected void configure(ServerHttpSecurity http) {
ReactiveAuthenticationManager authenticationManager = getAuthenticationManager();
AuthenticationWebFilter oauth2 = new AuthenticationWebFilter(authenticationManager);
oauth2.setServerAuthenticationConverter(OAuth2ResourceServerSpec.this.bearerTokenConverter);
oauth2.setAuthenticationFailureHandler(
new ServerAuthenticationEntryPointFailureHandler(OAuth2ResourceServerSpec.this.entryPoint));
http.addFilterAt(oauth2, SecurityWebFiltersOrder.AUTHENTICATION);
}
protected ReactiveJwtDecoder getJwtDecoder() {
return (this.jwtDecoder != null) ? this.jwtDecoder : getBean(ReactiveJwtDecoder.class);
}
protected Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> getJwtAuthenticationConverter() {
if (this.jwtAuthenticationConverter != null) {
return this.jwtAuthenticationConverter;
}
if (getBeanNamesForTypeOrEmpty(ReactiveJwtAuthenticationConverter.class).length > 0) {
return getBean(ReactiveJwtAuthenticationConverter.class);
}
else {
return new ReactiveJwtAuthenticationConverter();
}
}
private ReactiveAuthenticationManager getAuthenticationManager() {
if (this.authenticationManager != null) {
return this.authenticationManager;
}
ReactiveJwtDecoder jwtDecoder = getJwtDecoder();
Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> jwtAuthenticationConverter = getJwtAuthenticationConverter();
JwtReactiveAuthenticationManager authenticationManager = new JwtReactiveAuthenticationManager(
jwtDecoder);
authenticationManager.setJwtAuthenticationConverter(jwtAuthenticationConverter);
return authenticationManager;
}
}
/**
* Configures Opaque Token Resource Server support
*
* @author Josh Cummings
* @since 5.2
*/
public final class OpaqueTokenSpec {
private String introspectionUri;
private String clientId;
private String clientSecret;
private Supplier<ReactiveOpaqueTokenIntrospector> introspector;
private OpaqueTokenSpec() {
}
/**
* Configures the URI of the Introspection endpoint
* @param introspectionUri The URI of the Introspection endpoint
* @return the {@code OpaqueTokenSpec} for additional configuration
*/
public OpaqueTokenSpec introspectionUri(String introspectionUri) {
Assert.hasText(introspectionUri, "introspectionUri cannot be empty");
this.introspectionUri = introspectionUri;
this.introspector = () -> new NimbusReactiveOpaqueTokenIntrospector(this.introspectionUri,
this.clientId, this.clientSecret);
return this;
}
/**
* Configures the credentials for Introspection endpoint
* @param clientId The clientId part of the credentials
* @param clientSecret The clientSecret part of the credentials
* @return the {@code OpaqueTokenSpec} for additional configuration
*/
public OpaqueTokenSpec introspectionClientCredentials(String clientId, String clientSecret) {
Assert.hasText(clientId, "clientId cannot be empty");
Assert.notNull(clientSecret, "clientSecret cannot be null");
this.clientId = clientId;
this.clientSecret = clientSecret;
this.introspector = () -> new NimbusReactiveOpaqueTokenIntrospector(this.introspectionUri,
this.clientId, this.clientSecret);
return this;
}
public OpaqueTokenSpec introspector(ReactiveOpaqueTokenIntrospector introspector) {
Assert.notNull(introspector, "introspector cannot be null");
this.introspector = () -> introspector;
return this;
}
/**
* Allows method chaining to continue configuring the
* {@link ServerHttpSecurity}
* @return the {@link ServerHttpSecurity} to continue configuring
*/
public OAuth2ResourceServerSpec and() {
return OAuth2ResourceServerSpec.this;
}
protected ReactiveAuthenticationManager getAuthenticationManager() {
return new OpaqueTokenReactiveAuthenticationManager(getIntrospector());
}
protected ReactiveOpaqueTokenIntrospector getIntrospector() {
if (this.introspector != null) {
return this.introspector.get();
}
return getBean(ReactiveOpaqueTokenIntrospector.class);
}
protected void configure(ServerHttpSecurity http) {
ReactiveAuthenticationManager authenticationManager = getAuthenticationManager();
AuthenticationWebFilter oauth2 = new AuthenticationWebFilter(authenticationManager);
oauth2.setServerAuthenticationConverter(OAuth2ResourceServerSpec.this.bearerTokenConverter);
oauth2.setAuthenticationFailureHandler(
new ServerAuthenticationEntryPointFailureHandler(OAuth2ResourceServerSpec.this.entryPoint));
http.addFilterAt(oauth2, SecurityWebFiltersOrder.AUTHENTICATION);
}
}
}
/**
* Configures anonymous authentication
*
* @since 5.2.0
*/
public final class AnonymousSpec {
private String key;
private AnonymousAuthenticationWebFilter authenticationFilter;
private Object principal = "anonymousUser";
private List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS");
/**
* Sets the key to identify tokens created for anonymous authentication. Default
* is a secure randomly generated key.
* @param key the key to identify tokens created for anonymous authentication.
* Default is a secure randomly generated key.
* @return the {@link AnonymousSpec} for further customization of anonymous
* authentication
*/
public AnonymousSpec key(String key) {
this.key = key;
return this;
}
/**
* Sets the principal for {@link Authentication} objects of anonymous users
* @param principal used for the {@link Authentication} object of anonymous users
* @return the {@link AnonymousSpec} for further customization of anonymous
* authentication
*/
public AnonymousSpec principal(Object principal) {
this.principal = principal;
return this;
}
/**
* Sets the
* {@link org.springframework.security.core.Authentication#getAuthorities()} for
* anonymous users
* @param authorities Sets the
* {@link org.springframework.security.core.Authentication#getAuthorities()} for
* anonymous users
* @return the {@link AnonymousSpec} for further customization of anonymous
* authentication
*/
public AnonymousSpec authorities(List<GrantedAuthority> authorities) {
this.authorities = authorities;
return this;
}
/**
* Sets the
* {@link org.springframework.security.core.Authentication#getAuthorities()} for
* anonymous users
* @param authorities Sets the
* {@link org.springframework.security.core.Authentication#getAuthorities()} for
* anonymous users (i.e. "ROLE_ANONYMOUS")
* @return the {@link AnonymousSpec} for further customization of anonymous
* authentication
*/
public AnonymousSpec authorities(String... authorities) {
return authorities(AuthorityUtils.createAuthorityList(authorities));
}
/**
* Sets the {@link AnonymousAuthenticationWebFilter} used to populate an anonymous
* user. If this is set, no attributes on the {@link AnonymousSpec} will be set on
* the {@link AnonymousAuthenticationWebFilter}.
* @param authenticationFilter the {@link AnonymousAuthenticationWebFilter} used
* to populate an anonymous user.
* @return the {@link AnonymousSpec} for further customization of anonymous
* authentication
*/
public AnonymousSpec authenticationFilter(AnonymousAuthenticationWebFilter authenticationFilter) {
this.authenticationFilter = authenticationFilter;
return this;
}
/**
* Allows method chaining to continue configuring the {@link ServerHttpSecurity}
* @return the {@link ServerHttpSecurity} to continue configuring
*/
public ServerHttpSecurity and() {
return ServerHttpSecurity.this;
}
/**
* Disables anonymous authentication.
* @return the {@link ServerHttpSecurity} to continue configuring
*/
public ServerHttpSecurity disable() {
ServerHttpSecurity.this.anonymous = null;
return ServerHttpSecurity.this;
}
protected void configure(ServerHttpSecurity http) {
if (this.authenticationFilter == null) {
this.authenticationFilter = new AnonymousAuthenticationWebFilter(getKey(), this.principal,
this.authorities);
}
http.addFilterAt(this.authenticationFilter, SecurityWebFiltersOrder.ANONYMOUS_AUTHENTICATION);
}
private String getKey() {
if (this.key == null) {
this.key = UUID.randomUUID().toString();
}
return this.key;
}
private AnonymousSpec() {
}
}
}
相关信息
相关文章
0
赞
热门推荐
-
2、 - 优质文章
-
3、 gate.io
-
8、 golang
-
9、 openharmony
-
10、 Vue中input框自动聚焦