For the latest stable version, please use Spring Security 6.1.12! |
Authorize HttpServletRequests with AuthorizationFilter
This section builds on Servlet Architecture and Implementation by digging deeper into how authorization works within Servlet-based applications.
AuthorizationFilter supersedes FilterSecurityInterceptor .
To remain backward compatible, FilterSecurityInterceptor remains the default.
This section discusses how AuthorizationFilter works and how to override the default configuration.
|
The AuthorizationFilter
provides authorization for HttpServletRequest
s.
It is inserted into the FilterChainProxy as one of the Security Filters.
You can override the default when you declare a SecurityFilterChain
.
Instead of using authorizeRequests
, use authorizeHttpRequests
, like so:
-
Java
@Bean
SecurityFilterChain web(HttpSecurity http) throws AuthenticationException {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated();
)
// ...
return http.build();
}
This improves on authorizeRequests
in a number of ways:
-
Uses the simplified
AuthorizationManager
API instead of metadata sources, config attributes, decision managers, and voters. This simplifies reuse and customization. -
Delays
Authentication
lookup. Instead of the authentication needing to be looked up for every request, it will only look it up in requests where an authorization decision requires authentication. -
Bean-based configuration support.
When authorizeHttpRequests
is used instead of authorizeRequests
, then AuthorizationFilter
is used instead of FilterSecurityInterceptor
.
-
First, the
AuthorizationFilter
obtains an Authentication from the SecurityContextHolder. It wraps this in anSupplier
in order to delay lookup. -
Second, it passes the
Supplier<Authentication>
and theHttpServletRequest
to theAuthorizationManager
.-
If authorization is denied, an
AccessDeniedException
is thrown. In this case theExceptionTranslationFilter
handles theAccessDeniedException
. -
If access is granted,
AuthorizationFilter
continues with the FilterChain which allows the application to process normally.
-
We can configure Spring Security to have different rules by adding more rules in order of precedence.
-
Java
@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
http
// ...
.authorizeHttpRequests(authorize -> authorize (1)
.requestMatchers("/resources/**", "/signup", "/about").permitAll() (2)
.requestMatchers("/admin/**").hasRole("ADMIN") (3)
.requestMatchers("/db/**").access(new WebExpressionAuthorizationManager("hasRole('ADMIN') and hasRole('DBA')")) (4)
// .requestMatchers("/db/**").access(AuthorizationManagers.allOf(AuthorityAuthorizationManager.hasRole("ADMIN"), AuthorityAuthorizationManager.hasRole("DBA"))) (5)
.anyRequest().denyAll() (6)
);
return http.build();
}
1 | There are multiple authorization rules specified. Each rule is considered in the order they were declared. |
2 | We specified multiple URL patterns that any user can access. Specifically, any user can access a request if the URL starts with "/resources/", equals "/signup", or equals "/about". |
3 | Any URL that starts with "/admin/" will be restricted to users who have the role "ROLE_ADMIN".
You will notice that since we are invoking the hasRole method we do not need to specify the "ROLE_" prefix. |
4 | Any URL that starts with "/db/" requires the user to have both "ROLE_ADMIN" and "ROLE_DBA".
You will notice that since we are using the hasRole expression we do not need to specify the "ROLE_" prefix. |
5 | The same rule from 4, could be written by combining multiple AuthorizationManager . |
6 | Any URL that has not already been matched on is denied access. This is a good strategy if you do not want to accidentally forget to update your authorization rules. |
You can take a bean-based approach by constructing your own RequestMatcherDelegatingAuthorizationManager
like so:
-
Java
@Bean
SecurityFilterChain web(HttpSecurity http, AuthorizationManager<RequestAuthorizationContext> access)
throws AuthenticationException {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().access(access)
)
// ...
return http.build();
}
@Bean
AuthorizationManager<RequestAuthorizationContext> requestMatcherAuthorizationManager(HandlerMappingIntrospector introspector) {
MvcRequestMatcher.Builder mvcMatcherBuilder = new MvcRequestMatcher.Builder(introspector);
RequestMatcher permitAll =
new AndRequestMatcher(
mvcMatcherBuilder.pattern("/resources/**"),
mvcMatcherBuilder.pattern("/signup"),
mvcMatcherBuilder.pattern("/about"));
RequestMatcher admin = mvcMatcherBuilder.pattern("/admin/**");
RequestMatcher db = mvcMatcherBuilder.pattern("/db/**");
RequestMatcher any = AnyRequestMatcher.INSTANCE;
AuthorizationManager<HttpServletRequest> manager = RequestMatcherDelegatingAuthorizationManager.builder()
.add(permitAll, (context) -> new AuthorizationDecision(true))
.add(admin, AuthorityAuthorizationManager.hasRole("ADMIN"))
.add(db, AuthorityAuthorizationManager.hasRole("DBA"))
.add(any, new AuthenticatedAuthorizationManager())
.build();
return (context) -> manager.check(context.getRequest());
}
You can also wire your own custom authorization managers for any request matcher.
Here is an example of mapping a custom authorization manager to the my/authorized/endpoint
:
-
Java
@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/my/authorized/endpoint").access(new CustomAuthorizationManager());
)
// ...
return http.build();
}
Or you can provide it for all requests as seen below:
-
Java
@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().access(new CustomAuthorizationManager());
)
// ...
return http.build();
}
By default, the AuthorizationFilter
applies to all dispatcher types.
We can configure Spring Security to not apply the authorization rules to all dispatcher types by using the shouldFilterAllDispatcherTypes
method:
-
Java
-
Kotlin
@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.shouldFilterAllDispatcherTypes(false)
.anyRequest().authenticated()
)
// ...
return http.build();
}
@Bean
open fun web(http: HttpSecurity): SecurityFilterChain {
http {
authorizeHttpRequests {
shouldFilterAllDispatcherTypes = false
authorize(anyRequest, authenticated)
}
}
return http.build()
}
Instead of setting shouldFilterAllDispatcherTypes
to false
, the recommended approach is to customize authorization on the dispatcher types.
For example, you may want to grant all access on requests with dispatcher type ASYNC
or FORWARD
.
-
Java
-
Kotlin
@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.dispatcherTypeMatchers(DispatcherType.ASYNC, DispatcherType.FORWARD).permitAll()
.anyRequest().authenticated()
)
// ...
return http.build();
}
@Bean
open fun web(http: HttpSecurity): SecurityFilterChain {
http {
authorizeHttpRequests {
authorize(DispatcherTypeRequestMatcher(DispatcherType.ASYNC, DispatcherType.FORWARD), permitAll)
authorize(anyRequest, authenticated)
}
}
return http.build()
}
You can also customize it to require a specific role for a dispatcher type:
-
Java
-
Kotlin
@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.dispatcherTypeMatchers(DispatcherType.ERROR).hasRole("ADMIN")
.anyRequest().authenticated()
)
// ...
return http.build();
}
@Bean
open fun web(http: HttpSecurity): SecurityFilterChain {
http {
authorizeHttpRequests {
authorize(DispatcherTypeRequestMatcher(DispatcherType.ERROR), hasRole("ADMIN"))
authorize(anyRequest, authenticated)
}
}
return http.build()
}
Request Matchers
The RequestMatcher
interface is used to determine if a request matches a given rule.
We use securityMatchers
to determine if a given HttpSecurity
should be applied to a given request.
The same way, we can use requestMatchers
to determine the authorization rules that we should apply to a given request.
Look at the following example:
-
Java
-
Kotlin
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.securityMatcher("/api/**") (1)
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/user/**").hasRole("USER") (2)
.requestMatchers("/admin/**").hasRole("ADMIN") (3)
.anyRequest().authenticated() (4)
)
.formLogin(withDefaults());
return http.build();
}
}
@Configuration
@EnableWebSecurity
open class SecurityConfig {
@Bean
open fun web(http: HttpSecurity): SecurityFilterChain {
http {
securityMatcher("/api/**") (1)
authorizeHttpRequests {
authorize("/user/**", hasRole("USER")) (2)
authorize("/admin/**", hasRole("ADMIN")) (3)
authorize(anyRequest, authenticated) (4)
}
}
return http.build()
}
}
1 | Configure HttpSecurity to only be applied to URLs that start with /api/ |
2 | Allow access to URLs that start with /user/ to users with the USER role |
3 | Allow access to URLs that start with /admin/ to users with the ADMIN role |
4 | Any other request that doesn’t match the rules above, will require authentication |
The securityMatcher(s)
and requestMatcher(s)
methods will decide which RequestMatcher
implementation fits best for your application: If Spring MVC is in the classpath, then MvcRequestMatcher
will be used, otherwise, AntPathRequestMatcher
will be used.
You can read more about the Spring MVC integration here.
If you want to use a specific RequestMatcher
, just pass an implementation to the securityMatcher
and/or requestMatcher
methods:
-
Java
-
Kotlin
import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher; (1)
import static org.springframework.security.web.util.matcher.RegexRequestMatcher.regexMatcher;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.securityMatcher(antMatcher("/api/**")) (2)
.authorizeHttpRequests(authorize -> authorize
.requestMatchers(antMatcher("/user/**")).hasRole("USER") (3)
.requestMatchers(regexMatcher("/admin/.*")).hasRole("ADMIN") (4)
.requestMatchers(new MyCustomRequestMatcher()).hasRole("SUPERVISOR") (5)
.anyRequest().authenticated()
)
.formLogin(withDefaults());
return http.build();
}
}
public class MyCustomRequestMatcher implements RequestMatcher {
@Override
public boolean matches(HttpServletRequest request) {
// ...
}
}
import org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher (1)
import org.springframework.security.web.util.matcher.RegexRequestMatcher.regexMatcher
@Configuration
@EnableWebSecurity
open class SecurityConfig {
@Bean
open fun web(http: HttpSecurity): SecurityFilterChain {
http {
securityMatcher(antMatcher("/api/**")) (2)
authorizeHttpRequests {
authorize(antMatcher("/user/**"), hasRole("USER")) (3)
authorize(regexMatcher("/admin/**"), hasRole("ADMIN")) (4)
authorize(MyCustomRequestMatcher(), hasRole("SUPERVISOR")) (5)
authorize(anyRequest, authenticated)
}
}
return http.build()
}
}
1 | Import the static factory methods from AntPathRequestMatcher and RegexRequestMatcher to create RequestMatcher instances. |
2 | Configure HttpSecurity to only be applied to URLs that start with /api/ , using AntPathRequestMatcher |
3 | Allow access to URLs that start with /user/ to users with the USER role, using AntPathRequestMatcher |
4 | Allow access to URLs that start with /admin/ to users with the ADMIN role, using RegexRequestMatcher |
5 | Allow access to URLs that match the MyCustomRequestMatcher to users with the SUPERVISOR role, using a custom RequestMatcher |
Expressions
It is recommended that you use type-safe authorization managers instead of SpEL.
However, WebExpressionAuthorizationManager
is available to help migrate legacy SpEL.
To use WebExpressionAuthorizationManager
, you can construct one with the expression you are trying to migrate, like so:
-
Java
-
Kotlin
.requestMatchers("/test/**").access(new WebExpressionAuthorizationManager("hasRole('ADMIN') && hasRole('USER')"))
.requestMatchers("/test/**").access(WebExpressionAuthorizationManager("hasRole('ADMIN') && hasRole('USER')"))
If you are referring to a bean in your expression like so: @webSecurity.check(authentication, request)
, it’s recommended that you instead call the bean directly, which will look something like the following:
-
Java
-
Kotlin
.requestMatchers("/test/**").access((authentication, context) ->
new AuthorizationDecision(webSecurity.check(authentication.get(), context.getRequest())))
.requestMatchers("/test/**").access((authentication, context): AuthorizationManager<RequestAuthorizationContext> ->
AuthorizationDecision(webSecurity.check(authentication.get(), context.getRequest())))
For complex instructions that include bean references as well as other expressions, it is recommended that you change those to implement AuthorizationManager
and refer to them by calling .access(AuthorizationManager)
.
If you are not able to do that, you can configure a DefaultHttpSecurityExpressionHandler
with a bean resolver and supply that to WebExpressionAuthorizationManager#setExpressionhandler
.