This version is still in development and is not considered stable yet. For the latest stable version, please use Spring Security 6.5.3! |
Java Configuration
General support for Java configuration was added to Spring Framework in Spring 3.1. Spring Security 3.2 introduced Java configuration to let users configure Spring Security without the use of any XML.
If you are familiar with the Security Namespace Configuration, you should find quite a few similarities between it and Spring Security Java configuration.
Spring Security provides lots of sample applications to demonstrate the use of Spring Security Java Configuration. |
Hello Web Security Java Configuration
The first step is to create our Spring Security Java Configuration.
The configuration creates a Servlet Filter known as the springSecurityFilterChain
, which is responsible for all the security (protecting the application URLs, validating submitted username and passwords, redirecting to the log in form, and so on) within your application.
The following example shows the most basic example of a Spring Security Java Configuration:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.*;
import org.springframework.security.config.annotation.authentication.builders.*;
import org.springframework.security.config.annotation.web.configuration.*;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
@Bean
public UserDetailsService userDetailsService() {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withDefaultPasswordEncoder().username("user").password("password").roles("USER").build());
return manager;
}
}
This configuration is not complex or extensive, but it does a lot:
-
Require authentication to every URL in your application
-
Generate a login form for you
-
Let the user with a Username of
user
and a Password ofpassword
authenticate with form based authentication -
Let the user logout
-
CSRF attack prevention
-
Session Fixation protection
-
Security Header integration:
-
HTTP Strict Transport Security for secure requests
-
X-Content-Type-Options integration
-
Cache Control (which you can override later in your application to allow caching of your static resources)
-
X-XSS-Protection integration
-
X-Frame-Options integration to help prevent Clickjacking
-
-
Integration with the following Servlet API methods:
AbstractSecurityWebApplicationInitializer
The next step is to register the springSecurityFilterChain
with the WAR file.
You can do so in Java configuration with Spring’s WebApplicationInitializer
support in a Servlet 3.0+ environment.
Not surprisingly, Spring Security provides a base class (AbstractSecurityWebApplicationInitializer
) to ensure that the springSecurityFilterChain
gets registered for you.
The way in which we use AbstractSecurityWebApplicationInitializer
differs depending on if we are already using Spring or if Spring Security is the only Spring component in our application.
-
AbstractSecurityWebApplicationInitializer without Existing Spring - Use these instructions if you are not already using Spring
-
AbstractSecurityWebApplicationInitializer with Spring MVC - Use these instructions if you are already using Spring
AbstractSecurityWebApplicationInitializer without Existing Spring
If you are not using Spring or Spring MVC, you need to pass the WebSecurityConfig
to the superclass to ensure the configuration is picked up:
import org.springframework.security.web.context.*;
public class SecurityWebApplicationInitializer
extends AbstractSecurityWebApplicationInitializer {
public SecurityWebApplicationInitializer() {
super(WebSecurityConfig.class);
}
}
The SecurityWebApplicationInitializer
:
-
Automatically registers the
springSecurityFilterChain
Filter for every URL in your application. -
Add a
ContextLoaderListener
that loads the WebSecurityConfig.
AbstractSecurityWebApplicationInitializer with Spring MVC
If we use Spring elsewhere in our application, we probably already have a WebApplicationInitializer
that is loading our Spring Configuration.
If we use the previous configuration, we would get an error.
Instead, we should register Spring Security with the existing ApplicationContext
.
For example, if we use Spring MVC, our SecurityWebApplicationInitializer
could look something like the following:
import org.springframework.security.web.context.*;
public class SecurityWebApplicationInitializer
extends AbstractSecurityWebApplicationInitializer {
}
This only registers the springSecurityFilterChain
for every URL in your application.
After that, we need to ensure that WebSecurityConfig
was loaded in our existing ApplicationInitializer
.
For example, if we use Spring MVC it is added in the getServletConfigClasses()
:
public class MvcWebApplicationInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { WebSecurityConfig.class, WebMvcConfig.class };
}
// ... other overrides ...
}
The reason for this is that Spring Security needs to be able to inspect some Spring MVC configuration in order to appropriately configure underlying request matchers, so they need to be in the same application context.
Placing Spring Security in getRootConfigClasses
places it into a parent application context that may not be able to find Spring MVC’s PathPatternParser
.
Configuring for Multiple Spring MVC Dispatchers
If desired, any Spring Security configuration that is unrelated to Spring MVC may be placed in a different configuration class like so:
public class MvcWebApplicationInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { NonWebSecurityConfig.class };
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { WebSecurityConfig.class, WebMvcConfig.class };
}
// ... other overrides ...
}
This can be helpful if you have multiple instances of AbstractAnnotationConfigDispatcherServletInitializer
and don’t want to duplicate the general security configuration across both of them.
HttpSecurity
Thus far, our WebSecurityConfig
contains only information about how to authenticate our users.
How does Spring Security know that we want to require all users to be authenticated?
How does Spring Security know we want to support form-based authentication?
Actually, there is a configuration class (called SecurityFilterChain
) that is being invoked behind the scenes.
It is configured with the following default implementation:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults())
.httpBasic(Customizer.withDefaults());
return http.build();
}
The default configuration (shown in the preceding example):
-
Ensures that any request to our application requires the user to be authenticated
-
Lets users authenticate with form-based login
-
Lets users authenticate with HTTP Basic authentication
Note that this configuration parallels the XML namespace configuration:
<http>
<intercept-url pattern="/**" access="authenticated"/>
<form-login />
<http-basic />
</http>
Multiple HttpSecurity Instances
To effectively manage security in an application where certain areas need different protection, we can employ multiple filter chains alongside the securityMatcher
DSL method.
This approach allows us to define distinct security configurations tailored to specific parts of the application, enhancing overall application security and control.
We can configure multiple HttpSecurity
instances just as we can have multiple <http>
blocks in XML.
The key is to register multiple SecurityFilterChain
@Bean
s.
The following example has a different configuration for URLs that begin with /api/
:
@Configuration
@EnableWebSecurity
public class MultiHttpSecurityConfig {
@Bean (1)
public UserDetailsService userDetailsService() throws Exception {
UserBuilder users = User.withDefaultPasswordEncoder();
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(users.username("user").password("password").roles("USER").build());
manager.createUser(users.username("admin").password("password").roles("USER","ADMIN").build());
return manager;
}
@Bean
@Order(1) (2)
public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
http
.securityMatcher("/api/**") (3)
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().hasRole("ADMIN")
)
.httpBasic(Customizer.withDefaults());
return http.build();
}
@Bean (4)
public SecurityFilterChain formLoginFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults());
return http.build();
}
}
1 | Configure Authentication as usual. |
2 | Create an instance of SecurityFilterChain that contains @Order to specify which SecurityFilterChain should be considered first. |
3 | The http.securityMatcher() states that this HttpSecurity is applicable only to URLs that begin with /api/ . |
4 | Create another instance of SecurityFilterChain .
If the URL does not begin with /api/ , this configuration is used.
This configuration is considered after apiFilterChain , since it has an @Order value after 1 (no @Order defaults to last). |
Choosing securityMatcher
or requestMatchers
A common question is:
What is the difference between the
http.securityMatcher()
method andrequestMatchers()
used for request authorization (i.e. inside ofhttp.authorizeHttpRequests()
)?
To answer this question, it helps to understand that each HttpSecurity
instance used to build a SecurityFilterChain
contains a RequestMatcher
to match incoming requests.
If a request does not match a SecurityFilterChain
with higher priority (e.g. @Order(1)
), the request can be tried against a filter chain with lower priority (e.g. no @Order
).
The matching logic for multiple filter chains is performed by the |
The default RequestMatcher
matches any request to ensure Spring Security protects all requests by default.
Specifying a |
If no filter chain matches a particular request, the request is not protected by Spring Security. |
The following example demonstrates a single filter chain that only protects requests that begin with /secured/
:
@Configuration
@EnableWebSecurity
public class PartialSecurityConfig {
@Bean
public UserDetailsService userDetailsService() throws Exception {
// ...
}
@Bean
public SecurityFilterChain securedFilterChain(HttpSecurity http) throws Exception {
http
.securityMatcher("/secured/**") (1)
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/secured/user").hasRole("USER") (2)
.requestMatchers("/secured/admin").hasRole("ADMIN") (3)
.anyRequest().authenticated() (4)
)
.httpBasic(Customizer.withDefaults())
.formLogin(Customizer.withDefaults());
return http.build();
}
}
1 | Requests that begin with /secured/ will be protected but any other requests are not protected. |
2 | Requests to /secured/user require the ROLE_USER authority. |
3 | Requests to /secured/admin require the ROLE_ADMIN authority. |
4 | Any other requests (such as /secured/other ) simply require an authenticated user. |
It is recommended to provide a |
Notice that the requestMatchers
method only applies to individual authorization rules.
Each request listed there must also match the overall securityMatcher
for this particular HttpSecurity
instance used to create the SecurityFilterChain
.
Using anyRequest()
in this example matches all other requests within this particular SecurityFilterChain
(which must begin with /secured/
).
See Authorize HttpServletRequests for more information on |
SecurityFilterChain
Endpoints
Several filters in the SecurityFilterChain
directly provide endpoints, such as the UsernamePasswordAuthenticationFilter
which is set up by http.formLogin()
and provides the POST /login
endpoint.
In the above example, the /login
endpoint is not matched by http.securityMatcher("/secured/**")
and therefore that application would not have any GET /login
or POST /login
endpoint.
Such requests would return 404 Not Found
.
This is often surprising to users.
Specifying http.securityMatcher()
affects what requests are matched by that SecurityFilterChain
.
However, it does not automatically affect endpoints provided by the filter chain.
In such cases, you may need to customize the URL of any endpoints you would like the filter chain to provide.
The following example demonstrates a configuration that secures requests that begin with /secured/
and denies all other requests, while also customizing endpoints provided by the SecurityFilterChain
:
@Configuration
@EnableWebSecurity
public class SecuredSecurityConfig {
@Bean
public UserDetailsService userDetailsService() throws Exception {
// ...
}
@Bean
@Order(1)
public SecurityFilterChain securedFilterChain(HttpSecurity http) throws Exception {
http
.securityMatcher("/secured/**") (1)
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated() (2)
)
.formLogin((formLogin) -> formLogin (3)
.loginPage("/secured/login")
.loginProcessingUrl("/secured/login")
.permitAll()
)
.logout((logout) -> logout (4)
.logoutUrl("/secured/logout")
.logoutSuccessUrl("/secured/login?logout")
.permitAll()
)
.formLogin(Customizer.withDefaults());
return http.build();
}
@Bean
public SecurityFilterChain defaultFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().denyAll() (5)
);
return http.build();
}
}
1 | Requests that begin with /secured/ will be protected by this filter chain. |
2 | Requests that begin with /secured/ require an authenticated user. |
3 | Customize form login to prefix URLs with /secured/ . |
4 | Customize logout to prefix URLs with /secured/ . |
5 | All other requests will be denied. |
This example customizes the login and logout pages, which disables Spring Security’s generated pages.
You must provide your own custom endpoints for |
Real World Example
The following example demonstrates a slightly more real-world configuration putting all of these elements together:
@Configuration
@EnableWebSecurity
public class BankingSecurityConfig {
@Bean (1)
public UserDetailsService userDetailsService() {
UserBuilder users = User.withDefaultPasswordEncoder();
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(users.username("user1").password("password").roles("USER", "VIEW_BALANCE").build());
manager.createUser(users.username("user2").password("password").roles("USER").build());
manager.createUser(users.username("admin").password("password").roles("ADMIN").build());
return manager;
}
@Bean
@Order(1) (2)
public SecurityFilterChain approvalsSecurityFilterChain(HttpSecurity http) throws Exception {
String[] approvalsPaths = { "/accounts/approvals/**", "/loans/approvals/**", "/credit-cards/approvals/**" };
http
.securityMatcher(approvalsPaths)
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().hasRole("ADMIN")
)
.httpBasic(Customizer.withDefaults());
return http.build();
}
@Bean
@Order(2) (3)
public SecurityFilterChain bankingSecurityFilterChain(HttpSecurity http) throws Exception {
String[] bankingPaths = { "/accounts/**", "/loans/**", "/credit-cards/**", "/balances/**" };
String[] viewBalancePaths = { "/balances/**" };
http
.securityMatcher(bankingPaths)
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers(viewBalancePaths).hasRole("VIEW_BALANCE")
.anyRequest().hasRole("USER")
);
return http.build();
}
@Bean (4)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
String[] allowedPaths = { "/", "/user-login", "/user-logout", "/notices", "/contact", "/register" };
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers(allowedPaths).permitAll()
.anyRequest().authenticated()
)
.formLogin((formLogin) -> formLogin
.loginPage("/user-login")
.loginProcessingUrl("/user-login")
)
.logout((logout) -> logout
.logoutUrl("/user-logout")
.logoutSuccessUrl("/?logout")
);
return http.build();
}
}
1 | Begin by configuring authentication settings. |
2 | Define a SecurityFilterChain instance with @Order(1) , which means that this filter chain will have the highest priority.
This filter chain applies only to requests that begin with /accounts/approvals/ , /loans/approvals/ or /credit-cards/approvals/ .
Requests to this filter chain require the ROLE_ADMIN authority and allow HTTP Basic Authentication. |
3 | Next, create another SecurityFilterChain instance with @Order(2) which will be considered second.
This filter chain applies only to requests that begin with /accounts/ , /loans/ , /credit-cards/ , or /balances/ .
Notice that because this filter chain is second, any requests that include /approvals/ will match the previous filter chain and will not be matched by this filter chain.
Requests to this filter chain require the ROLE_USER authority.
This filter chain does not define any authentication because the next (default) filter chain contains that configuration. |
4 | Lastly, create an additional SecurityFilterChain instance without an @Order annotation.
This configuration will handle requests not covered by the other filter chains and will be processed last (no @Order defaults to last).
Requests that match / , /user-login , /user-logout , /notices , /contact and /register allow access without authentication.
Any other requests require the user to be authenticated to access any URL not explicitly allowed or protected by other filter chains. |
Custom DSLs
You can provide your own custom DSLs in Spring Security:
-
Java
-
Kotlin
public class MyCustomDsl extends AbstractHttpConfigurer<MyCustomDsl, HttpSecurity> {
private boolean flag;
@Override
public void init(HttpSecurity http) throws Exception {
// any method that adds another configurer
// must be done in the init method
http.csrf().disable();
}
@Override
public void configure(HttpSecurity http) throws Exception {
ApplicationContext context = http.getSharedObject(ApplicationContext.class);
// here we lookup from the ApplicationContext. You can also just create a new instance.
MyFilter myFilter = context.getBean(MyFilter.class);
myFilter.setFlag(flag);
http.addFilterBefore(myFilter, UsernamePasswordAuthenticationFilter.class);
}
public MyCustomDsl flag(boolean value) {
this.flag = value;
return this;
}
public static MyCustomDsl customDsl() {
return new MyCustomDsl();
}
}
class MyCustomDsl : AbstractHttpConfigurer<MyCustomDsl, HttpSecurity>() {
var flag: Boolean = false
override fun init(http: HttpSecurity) {
// any method that adds another configurer
// must be done in the init method
http.csrf().disable()
}
override fun configure(http: HttpSecurity) {
val context: ApplicationContext = http.getSharedObject(ApplicationContext::class.java)
// here we lookup from the ApplicationContext. You can also just create a new instance.
val myFilter: MyFilter = context.getBean(MyFilter::class.java)
myFilter.setFlag(flag)
http.addFilterBefore(myFilter, UsernamePasswordAuthenticationFilter::class.java)
}
companion object {
@JvmStatic
fun customDsl(): MyCustomDsl {
return MyCustomDsl()
}
}
}
This is actually how methods like |
You can then use the custom DSL:
-
Java
-
Kotlin
@Configuration
@EnableWebSecurity
public class Config {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.with(MyCustomDsl.customDsl(), (dsl) -> dsl
.flag(true)
)
// ...
return http.build();
}
}
@Configuration
@EnableWebSecurity
class Config {
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
http
.with(MyCustomDsl.customDsl()) {
flag = true
}
// ...
return http.build()
}
}
The code is invoked in the following order:
-
Code in the
Config.filterChain
method is invoked -
Code in the
MyCustomDsl.init
method is invoked -
Code in the
MyCustomDsl.configure
method is invoked
If you want, you can have HttpSecurity
add MyCustomDsl
by default by using SpringFactories
.
For example, you can create a resource on the classpath named META-INF/spring.factories
with the following contents:
org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer = sample.MyCustomDsl
You can also explicit disable the default:
-
Java
-
Kotlin
@Configuration
@EnableWebSecurity
public class Config {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.with(MyCustomDsl.customDsl(), (dsl) -> dsl
.disable()
)
...;
return http.build();
}
}
@Configuration
@EnableWebSecurity
class Config {
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
http
.with(MyCustomDsl.customDsl()) {
disable()
}
// ...
return http.build()
}
}
Modular HttpSecurity Configuration
Many users prefer that their Spring Security configuration lives in a centralized place and will choose to configure it in a single SecurityFilterChain
instance.
However, there are times that users may want to modularize the configuration.
This can be done using:
If you are using Spring Security’s Kotlin Configuration, then you can also expose *Dsl → Unit Beans as outlined in Modular HttpSecurityDsl Configuration.
|
Customizer<HttpSecurity> Beans
If you would like to modularize your security configuration you can place logic in a Customizer<HttpSecurity>
Bean.
For example, the following configuration will ensure all HttpSecurity
instances are configured to:
-
Java
-
Kotlin
@Bean
ThrowingCustomizer<HttpSecurity> httpSecurityCustomizer() {
return (http) -> http
.headers((headers) -> headers
.contentSecurityPolicy((csp) -> csp
(1)
.policyDirectives("object-src 'none'")
)
)
(2)
.redirectToHttps(Customizer.withDefaults());
}
@Bean
fun httpSecurityCustomizer(): ThrowingCustomizer<HttpSecurity> {
return ThrowingCustomizer { http -> http
.headers { headers -> headers
.contentSecurityPolicy { csp -> csp
(1)
.policyDirectives("object-src 'none'")
}
}
(2)
.redirectToHttps(Customizer.withDefaults())
}
}
1 | Set the Content Security Policy to object-src 'none' |
2 | Redirect any request to https |
Top Level HttpSecurity Customizer Beans
If you prefer to have further modularization of your security configuration, Spring Security will automatically apply any top level HttpSecurity
Customizer
Beans.
A top level HttpSecurity
Customizer
type can be summarized as any Customizer<T>
that matches public HttpSecurity.*(Customizer<T>)
.
This translates to any Customizer<T>
that is a single argument to a public method on HttpSecurity
.
A few examples can help to clarify.
If Customizer<ContentTypeOptionsConfig>
is published as a Bean, it will not be be automatically applied because it is an argument to HeadersConfigurer.contentTypeOptions(Customizer)
which is not a method defined on HttpSecurity
.
However, if Customizer<HeadersConfigurer<HttpSecurity>>
is published as a Bean, it will be automatically applied because it is an argument to HttpSecurity.headers(Customizer)
.
For example, the following configuration will ensure that the Content Security Policy is set to object-src 'none'
:
-
Java
-
Kotlin
@Bean
Customizer<HeadersConfigurer<HttpSecurity>> headersSecurity() {
return (headers) -> headers
.contentSecurityPolicy((csp) -> csp
(1)
.policyDirectives("object-src 'none'")
);
}
@Bean
fun headersSecurity(): Customizer<HeadersConfigurer<HttpSecurity>> {
return Customizer { headers -> headers
.contentSecurityPolicy { csp -> csp
(1)
.policyDirectives("object-src 'none'")
}
}
}
Customizer Bean Ordering
First each Customizer<HttpSecurity> Bean is applied using ObjectProvider#orderedStream().
This means that if there are multiple Customizer<HttpSecurity>
Beans, the @Order annotation can be added to the Bean definitions to control the ordering.
Next every Top Level HttpSecurity Customizer Beans type is looked up and each is is applied using ObjectProvider#orderedStream()
.
If there is are two Customizer<HeadersConfigurer<HttpSecurity>>
beans and two Customizer<HttpsRedirectConfigurer<HttpSecurity>>
instances, the order that each Customizer
type is invoked is undefined.
However, the order that each instance of Customizer<HttpsRedirectConfigurer<HttpSecurity>>
is defined by ObjectProvider#orderedStream()
and can be controlled using @Order
on the Bean the definitions.
Finally, the HttpSecurity
Bean is injected as a Bean.
All Customizer
instances are applied before the HttpSecurity
Bean is created.
This allows overriding the customizations provided by the Customizer
Beans.
You can find an example below that illustrates the ordering:
-
Java
-
Kotlin
@Bean (4)
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((requests) -> requests
.anyRequest().authenticated()
);
return http.build();
}
@Bean
@Order(Ordered.LOWEST_PRECEDENCE) (2)
ThrowingCustomizer<HttpSecurity> userAuthorization() {
return (http) -> http
.authorizeHttpRequests((requests) -> requests
.requestMatchers("/users/**").hasRole("USER")
);
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE) (1)
ThrowingCustomizer<HttpSecurity> adminAuthorization() {
return (http) -> http
.authorizeHttpRequests((requests) -> requests
.requestMatchers("/admins/**").hasRole("ADMIN")
);
}
(3)
@Bean
Customizer<HeadersConfigurer<HttpSecurity>> contentSecurityPolicy() {
return (headers) -> headers
.contentSecurityPolicy((csp) -> csp
.policyDirectives("object-src 'none'")
);
}
@Bean
Customizer<HeadersConfigurer<HttpSecurity>> contentTypeOptions() {
return (headers) -> headers
.contentTypeOptions(Customizer.withDefaults());
}
@Bean
Customizer<HttpsRedirectConfigurer<HttpSecurity>> httpsRedirect() {
return Customizer.withDefaults();
}
@Bean (4)
fun springSecurity(http: HttpSecurity): SecurityFilterChain {
http
.authorizeHttpRequests({ requests -> requests
.anyRequest().authenticated()
})
return http.build()
}
@Bean
@Order(Ordered.LOWEST_PRECEDENCE) (2)
fun userAuthorization(): ThrowingCustomizer<HttpSecurity> {
return ThrowingCustomizer { http -> http
.authorizeHttpRequests { requests -> requests
.requestMatchers("/users/**").hasRole("USER")
}
}
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE) (1)
fun adminAuthorization(): ThrowingCustomizer<HttpSecurity> {
return ThrowingCustomizer { http -> http
.authorizeHttpRequests { requests -> requests
.requestMatchers("/admins/**").hasRole("ADMIN")
}
}
}
(3)
@Bean
fun contentSecurityPolicy(): Customizer<HeadersConfigurer<HttpSecurity>> {
return Customizer { headers -> headers
.contentSecurityPolicy { csp -> csp
.policyDirectives("object-src 'none'")
}
}
}
@Bean
fun contentTypeOptions(): Customizer<HeadersConfigurer<HttpSecurity>> {
return Customizer { headers -> headers
.contentTypeOptions(Customizer.withDefaults())
}
}
@Bean
fun httpsRedirect(): Customizer<HttpsRedirectConfigurer<HttpSecurity>> {
return Customizer.withDefaults<HttpsRedirectConfigurer<HttpSecurity>>()
}
1 | First all Customizer<HttpSecurity> instances are applied.
The adminAuthorization Bean has the highest @Order so it is applied first.
If there are no @Order annotations on the Customizer<HttpSecurity> Beans or the @Order annotations had the same value, then the order that the Customizer<HttpSecurity> instances are applied is undefined. |
2 | The userAuthorization is applied next due to being an instance of Customizer<HttpSecurity> |
3 | The order that the Customizer types are undefined.
In this example, the order of contentSecurityPolicy , contentTypeOptions , and httpsRedirect are undefined.
If @Order(Ordered.HIGHEST_PRECEDENCE) was added to contentTypeOptions , then we would know that contentTypeOptions is before contentSecurityPolicy (they are the same type), but we do not know if httpsRedirect is before or after the Customizer<HeadersConfigurer<HttpSecurity>> Beans. |
4 | After all of the Customizer Beans are applied, the HttpSecurity is passed in as a Bean. |
Post Processing Configured Objects
Spring Security’s Java configuration does not expose every property of every object that it configures. This simplifies the configuration for a majority of users. After all, if every property were exposed, users could use standard bean configuration.
While there are good reasons to not directly expose every property, users may still need more advanced configuration options.
To address this issue, Spring Security introduces the concept of an ObjectPostProcessor
, which can be used to modify or replace many of the Object
instances created by the Java Configuration.
For example, to configure the filterSecurityPublishAuthorizationSuccess
property on FilterSecurityInterceptor
, you can use the following:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
public <O extends FilterSecurityInterceptor> O postProcess(
O fsi) {
fsi.setPublishAuthorizationSuccess(true);
return fsi;
}
})
);
return http.build();
}