This version is still in development and is not considered stable yet. For the latest stable version, please use Spring Security 6.3.2! |
Authorize HttpServletRequests
Spring Security allows you to model your authorization at the request level.
For example, with Spring Security you can say that all pages under /admin
require one authority while all other pages simply require authentication.
By default, Spring Security requires that every request be authenticated.
That said, any time you use an HttpSecurity
instance, it’s necessary to declare your authorization rules.
Whenever you have an HttpSecurity
instance, you should at least do:
-
Java
-
Kotlin
-
Xml
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
http {
authorizeHttpRequests {
authorize(anyRequest, authenticated)
}
}
<http>
<intercept-url pattern="/**" access="authenticated"/>
</http>
This tells Spring Security that any endpoint in your application requires that the security context at a minimum be authenticated in order to allow it.
In many cases, your authorization rules will be more sophisticated than that, so please consider the following use cases:
-
I have an app that uses
authorizeRequests
and I want to migrate it toauthorizeHttpRequests
-
I want to understand how the
AuthorizationFilter
components work -
I want to match requests based on a pattern; specifically regex
-
I want to match request, and I map Spring MVC to something other than the default servlet
-
I want to authorize requests
-
I want to match a request programmatically
-
I want to authorize a request programmatically
-
I want to delegate request authorization to a policy agent
Understanding How Request Authorization Components Work
This section builds on Servlet Architecture and Implementation by digging deeper into how authorization works at the request level in Servlet-based applications. |
![authorizationfilter](../../_images/servlet/authorization/authorizationfilter.png)
-
First, the
AuthorizationFilter
constructs aSupplier
that retrieves an Authentication from the SecurityContextHolder. -
Second, it passes the
Supplier<Authentication>
and theHttpServletRequest
to theAuthorizationManager
. TheAuthorizationManager
matches the request to the patterns inauthorizeHttpRequests
, and runs the corresponding rule.-
If authorization is denied, an
AuthorizationDeniedEvent
is published, and anAccessDeniedException
is thrown. In this case theExceptionTranslationFilter
handles theAccessDeniedException
. -
If access is granted, an
AuthorizationGrantedEvent
is published andAuthorizationFilter
continues with the FilterChain which allows the application to process normally.
-
AuthorizationFilter
Is Last By Default
The AuthorizationFilter
is last in the Spring Security filter chain by default.
This means that Spring Security’s authentication filters, exploit protections, and other filter integrations do not require authorization.
If you add filters of your own before the AuthorizationFilter
, they will also not require authorization; otherwise, they will.
A place where this typically becomes important is when you are adding Spring MVC endpoints.
Because they are executed by the DispatcherServlet
and this comes after the AuthorizationFilter
, your endpoints need to be included in authorizeHttpRequests
to be permitted.
All Dispatches Are Authorized
The AuthorizationFilter
runs not just on every request, but on every dispatch.
This means that the REQUEST
dispatch needs authorization, but also FORWARD
s, ERROR
s, and INCLUDE
s.
For example, Spring MVC can FORWARD
the request to a view resolver that renders a Thymeleaf template, like so:
-
Java
-
Kotlin
@Controller
public class MyController {
@GetMapping("/endpoint")
public String endpoint() {
return "endpoint";
}
}
@Controller
class MyController {
@GetMapping("/endpoint")
fun endpoint(): String {
return "endpoint"
}
}
In this case, authorization happens twice; once for authorizing /endpoint
and once for forwarding to Thymeleaf to render the "endpoint" template.
For that reason, you may want to permit all FORWARD
dispatches.
Another example of this principle is how Spring Boot handles errors. If the container catches an exception, say like the following:
-
Java
-
Kotlin
@Controller
public class MyController {
@GetMapping("/endpoint")
public String endpoint() {
throw new UnsupportedOperationException("unsupported");
}
}
@Controller
class MyController {
@GetMapping("/endpoint")
fun endpoint(): String {
throw UnsupportedOperationException("unsupported")
}
}
then Boot will dispatch it to the ERROR
dispatch.
In that case, authorization also happens twice; once for authorizing /endpoint
and once for dispatching the error.
For that reason, you may want to permit all ERROR
dispatches.
Authentication
Lookup is Deferred
Remember that the AuthorizationManager
API uses a Supplier<Authentication>
.
This matters with authorizeHttpRequests
when requests are always permitted or always denied.
In those cases, the Authentication
is not queried, making for a faster request.
Authorizing an Endpoint
You can configure Spring Security to have different rules by adding more rules in order of precedence.
If you want to require that /endpoint
only be accessible by end users with the USER
authority, then you can do:
-
Java
-
Kotlin
-
Xml
@Bean
public SecurityFilterChain web(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/endpoint").hasAuthority("USER")
.anyRequest().authenticated()
)
// ...
return http.build();
}
@Bean
fun web(http: HttpSecurity): SecurityFilterChain {
http {
authorizeHttpRequests {
authorize("/endpoint", hasAuthority("USER"))
authorize(anyRequest, authenticated)
}
}
return http.build()
}
<http>
<intercept-url pattern="/endpoint" access="hasAuthority('USER')"/>
<intercept-url pattern="/**" access="authenticated"/>
</http>
As you can see, the declaration can be broken up in to pattern/rule pairs.
AuthorizationFilter
processes these pairs in the order listed, applying only the first match to the request.
This means that even though /**
would also match for /endpoint
the above rules are not a problem.
The way to read the above rules is "if the request is /endpoint
, then require the USER
authority; else, only require authentication".
Spring Security supports several patterns and several rules; you can also programmatically create your own of each.
Once authorized, you can test it using Security’s test support in the following way:
-
Java
@WithMockUser(authorities="USER")
@Test
void endpointWhenUserAuthorityThenAuthorized() {
this.mvc.perform(get("/endpoint"))
.andExpect(status().isOk());
}
@WithMockUser
@Test
void endpointWhenNotUserAuthorityThenForbidden() {
this.mvc.perform(get("/endpoint"))
.andExpect(status().isForbidden());
}
@Test
void anyWhenUnauthenticatedThenUnauthorized() {
this.mvc.perform(get("/any"))
.andExpect(status().isUnauthorized());
}
Matching Requests
Above you’ve already seen two ways to match requests.
The first you saw was the simplest, which is to match any request.
The second is to match by a URI pattern. Spring Security supports two languages for URI pattern-matching: Ant (as seen above) and Regular Expressions.
Matching Using Ant
Ant is the default language that Spring Security uses to match requests.
You can use it to match a single endpoint or a directory, and you can even capture placeholders for later use. You can also refine it to match a specific set of HTTP methods.
Let’s say that you instead of wanting to match the /endpoint
endpoint, you want to match all endpoints under the /resource
directory.
In that case, you can do something like the following:
-
Java
-
Kotlin
-
Xml
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/resource/**").hasAuthority("USER")
.anyRequest().authenticated()
)
http {
authorizeHttpRequests {
authorize("/resource/**", hasAuthority("USER"))
authorize(anyRequest, authenticated)
}
}
<http>
<intercept-url pattern="/resource/**" access="hasAuthority('USER')"/>
<intercept-url pattern="/**" access="authenticated"/>
</http>
The way to read this is "if the request is /resource
or some subdirectory, require the USER
authority; otherwise, only require authentication"
You can also extract path values from the request, as seen below:
-
Java
-
Kotlin
-
Xml
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/resource/{name}").access(new WebExpressionAuthorizationManager("#name == authentication.name"))
.anyRequest().authenticated()
)
http {
authorizeHttpRequests {
authorize("/resource/{name}", WebExpressionAuthorizationManager("#name == authentication.name"))
authorize(anyRequest, authenticated)
}
}
<http>
<intercept-url pattern="/resource/{name}" access="#name == authentication.name"/>
<intercept-url pattern="/**" access="authenticated"/>
</http>
Once authorized, you can test it using Security’s test support in the following way:
-
Java
@WithMockUser(authorities="USER")
@Test
void endpointWhenUserAuthorityThenAuthorized() {
this.mvc.perform(get("/endpoint/jon"))
.andExpect(status().isOk());
}
@WithMockUser
@Test
void endpointWhenNotUserAuthorityThenForbidden() {
this.mvc.perform(get("/endpoint/jon"))
.andExpect(status().isForbidden());
}
@Test
void anyWhenUnauthenticatedThenUnauthorized() {
this.mvc.perform(get("/any"))
.andExpect(status().isUnauthorized());
}
Spring Security only matches paths. If you want to match query parameters, you will need a custom request matcher. |
Matching Using Regular Expressions
Spring Security supports matching requests against a regular expression.
This can come in handy if you want to apply more strict matching criteria than **
on a subdirectory.
For example, consider a path that contains the username and the rule that all usernames must be alphanumeric.
You can use RegexRequestMatcher
to respect this rule, like so:
-
Java
-
Kotlin
-
Xml
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers(RegexRequestMatcher.regexMatcher("/resource/[A-Za-z0-9]+")).hasAuthority("USER")
.anyRequest().denyAll()
)
http {
authorizeHttpRequests {
authorize(RegexRequestMatcher.regexMatcher("/resource/[A-Za-z0-9]+"), hasAuthority("USER"))
authorize(anyRequest, denyAll)
}
}
<http>
<intercept-url request-matcher="regex" pattern="/resource/[A-Za-z0-9]+" access="hasAuthority('USER')"/>
<intercept-url pattern="/**" access="denyAll"/>
</http>
Matching By Http Method
You can also match rules by HTTP method.
One place where this is handy is when authorizing by permissions granted, like being granted a read
or write
privilege.
To require all GET
s to have the read
permission and all POST
s to have the write
permission, you can do something like this:
-
Java
-
Kotlin
-
Xml
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers(HttpMethod.GET).hasAuthority("read")
.requestMatchers(HttpMethod.POST).hasAuthority("write")
.anyRequest().denyAll()
)
http {
authorizeHttpRequests {
authorize(HttpMethod.GET, hasAuthority("read"))
authorize(HttpMethod.POST, hasAuthority("write"))
authorize(anyRequest, denyAll)
}
}
<http>
<intercept-url http-method="GET" pattern="/**" access="hasAuthority('read')"/>
<intercept-url http-method="POST" pattern="/**" access="hasAuthority('write')"/>
<intercept-url pattern="/**" access="denyAll"/>
</http>
These authorization rules should read as: "if the request is a GET, then require read
permission; else, if the request is a POST, then require write
permission; else, deny the request"
Denying the request by default is a healthy security practice since it turns the set of rules into an allow list. |
Once authorized, you can test it using Security’s test support in the following way:
-
Java
@WithMockUser(authorities="read")
@Test
void getWhenReadAuthorityThenAuthorized() {
this.mvc.perform(get("/any"))
.andExpect(status().isOk());
}
@WithMockUser
@Test
void getWhenNoReadAuthorityThenForbidden() {
this.mvc.perform(get("/any"))
.andExpect(status().isForbidden());
}
@WithMockUser(authorities="write")
@Test
void postWhenWriteAuthorityThenAuthorized() {
this.mvc.perform(post("/any").with(csrf()))
.andExpect(status().isOk());
}
@WithMockUser(authorities="read")
@Test
void postWhenNoWriteAuthorityThenForbidden() {
this.mvc.perform(get("/any").with(csrf()))
.andExpect(status().isForbidden());
}
Matching By Dispatcher Type
This feature is not currently supported in XML |
As stated earlier, Spring Security authorizes all dispatcher types by default.
And even though the security context established on the REQUEST
dispatch carries over to subsequent dispatches, subtle mismatches can sometimes cause an unexpected AccessDeniedException
.
To address that, you can configure Spring Security Java configuration to allow dispatcher types like FORWARD
and ERROR
, like so:
-
Java
-
Kotlin
http
.authorizeHttpRequests((authorize) -> authorize
.dispatcherTypeMatchers(DispatcherType.FORWARD, DispatcherType.ERROR).permitAll()
.requestMatchers("/endpoint").permitAll()
.anyRequest().denyAll()
)
http {
authorizeHttpRequests {
authorize(DispatcherTypeRequestMatcher(DispatcherType.FORWARD), permitAll)
authorize(DispatcherTypeRequestMatcher(DispatcherType.ERROR), permitAll)
authorize("/endpoint", permitAll)
authorize(anyRequest, denyAll)
}
}
Using an MvcRequestMatcher
Generally speaking, you can use requestMatchers(String)
as demonstrated above.
However, if you map Spring MVC to a different servlet path, then you need to account for that in your security configuration.
For example, if Spring MVC is mapped to /spring-mvc
instead of /
(the default), then you may have an endpoint like /spring-mvc/my/controller
that you want to authorize.
You need to use MvcRequestMatcher
to split the servlet path and the controller path in your configuration like so:
-
Java
-
Kotlin
-
Xml
@Bean
MvcRequestMatcher.Builder mvc(HandlerMappingIntrospector introspector) {
return new MvcRequestMatcher.Builder(introspector).servletPath("/spring-mvc");
}
@Bean
SecurityFilterChain appEndpoints(HttpSecurity http, MvcRequestMatcher.Builder mvc) {
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers(mvc.pattern("/my/controller/**")).hasAuthority("controller")
.anyRequest().authenticated()
);
return http.build();
}
@Bean
fun mvc(introspector: HandlerMappingIntrospector): MvcRequestMatcher.Builder =
MvcRequestMatcher.Builder(introspector).servletPath("/spring-mvc");
@Bean
fun appEndpoints(http: HttpSecurity, mvc: MvcRequestMatcher.Builder): SecurityFilterChain =
http {
authorizeHttpRequests {
authorize(mvc.pattern("/my/controller/**"), hasAuthority("controller"))
authorize(anyRequest, authenticated)
}
}
<http>
<intercept-url servlet-path="/spring-mvc" pattern="/my/controller/**" access="hasAuthority('controller')"/>
<intercept-url pattern="/**" access="authenticated"/>
</http>
This need can arise in at least two different ways:
-
If you use the
spring.mvc.servlet.path
Boot property to change the default path (/
) to something else -
If you register more than one Spring MVC
DispatcherServlet
(thus requiring that one of them not be the default path)
Using a Custom Matcher
This feature is not currently supported in XML |
In Java configuration, you can create your own RequestMatcher
and supply it to the DSL like so:
-
Java
-
Kotlin
RequestMatcher printview = (request) -> request.getParameter("print") != null;
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers(printview).hasAuthority("print")
.anyRequest().authenticated()
)
val printview: RequestMatcher = { (request) -> request.getParameter("print") != null }
http {
authorizeHttpRequests {
authorize(printview, hasAuthority("print"))
authorize(anyRequest, authenticated)
}
}
Because RequestMatcher is a functional interface, you can supply it as a lambda in the DSL.
However, if you want to extract values from the request, you will need to have a concrete class since that requires overriding a default method.
|
Once authorized, you can test it using Security’s test support in the following way:
-
Java
@WithMockUser(authorities="print")
@Test
void printWhenPrintAuthorityThenAuthorized() {
this.mvc.perform(get("/any?print"))
.andExpect(status().isOk());
}
@WithMockUser
@Test
void printWhenNoPrintAuthorityThenForbidden() {
this.mvc.perform(get("/any?print"))
.andExpect(status().isForbidden());
}
Authorizing Requests
Once a request is matched, you can authorize it in several ways already seen like permitAll
, denyAll
, and hasAuthority
.
As a quick summary, here are the authorization rules built into the DSL:
-
permitAll
- The request requires no authorization and is a public endpoint; note that in this case, theAuthentication
is never retrieved from the session -
denyAll
- The request is not allowed under any circumstances; note that in this case, theAuthentication
is never retrieved from the session -
hasAuthority
- The request requires that theAuthentication
have aGrantedAuthority
that matches the given value -
hasRole
- A shortcut forhasAuthority
that prefixesROLE_
or whatever is configured as the default prefix -
hasAnyAuthority
- The request requires that theAuthentication
have aGrantedAuthority
that matches any of the given values -
hasAnyRole
- A shortcut forhasAnyAuthority
that prefixesROLE_
or whatever is configured as the default prefix -
access
- The request uses this customAuthorizationManager
to determine access
Having now learned the patterns, rules, and how they can be paired together, you should be able to understand what is going on in this more complex example:
-
Java
import static jakarta.servlet.DispatcherType.*;
import static org.springframework.security.authorization.AuthorizationManagers.allOf;
import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasAuthority;
import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasRole;
@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
http
// ...
.authorizeHttpRequests(authorize -> authorize (1)
.dispatcherTypeMatchers(FORWARD, ERROR).permitAll() (2)
.requestMatchers("/static/**", "/signup", "/about").permitAll() (3)
.requestMatchers("/admin/**").hasRole("ADMIN") (4)
.requestMatchers("/db/**").access(allOf(hasAuthority("db"), hasRole("ADMIN"))) (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 | Dispatches FORWARD and ERROR are permitted to allow Spring MVC to render views and Spring Boot to render errors |
3 | We specified multiple URL patterns that any user can access. Specifically, any user can access a request if the URL starts with "/static/", equals "/signup", or equals "/about". |
4 | 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. |
5 | Any URL that starts with "/db/" requires the user to have both been granted the "db" permission as well as be a "ROLE_ADMIN".
You will notice that since we are using the hasRole expression we do not need to specify the "ROLE_" prefix. |
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. |
Expressing Authorization with SpEL
While using a concrete AuthorizationManager
is recommended, there are some cases where an expression is necessary, like with <intercept-url>
or with JSP Taglibs.
For that reason, this section will focus on examples from those domains.
Given that, let’s cover Spring Security’s Web Security Authorization SpEL API a bit more in depth.
Spring Security encapsulates all of its authorization fields and methods in a set of root objects.
The most generic root object is called SecurityExpressionRoot
and it forms the basis for WebSecurityExpressionRoot
.
Spring Security supplies this root object to StandardEvaluationContext
when preparing to evaluate an authorization expression.
Using Authorization Expression Fields and Methods
The first thing this provides is an enhanced set of authorization fields and methods to your SpEL expressions. What follows is a quick overview of the most common methods:
-
permitAll
- The request requires no authorization to be invoked; note that in this case, theAuthentication
is never retrieved from the session -
denyAll
- The request is not allowed under any circumstances; note that in this case, theAuthentication
is never retrieved from the session -
hasAuthority
- The request requires that theAuthentication
have aGrantedAuthority
that matches the given value -
hasRole
- A shortcut forhasAuthority
that prefixesROLE_
or whatever is configured as the default prefix -
hasAnyAuthority
- The request requires that theAuthentication
have aGrantedAuthority
that matches any of the given values -
hasAnyRole
- A shortcut forhasAnyAuthority
that prefixesROLE_
or whatever is configured as the default prefix -
hasPermission
- A hook into yourPermissionEvaluator
instance for doing object-level authorization
And here is a brief look at the most common fields:
-
authentication
- TheAuthentication
instance associated with this method invocation -
principal
- TheAuthentication#getPrincipal
associated with this method invocation
Having now learned the patterns, rules, and how they can be paired together, you should be able to understand what is going on in this more complex example:
-
Xml
<http>
<intercept-url pattern="/static/**" access="permitAll"/> (1)
<intercept-url pattern="/admin/**" access="hasRole('ADMIN')"/> (2)
<intercept-url pattern="/db/**" access="hasAuthority('db') and hasRole('ADMIN')"/> (3)
<intercept-url pattern="/**" access="denyAll"/> (4)
</http>
1 | We specified a URL pattern that any user can access. Specifically, any user can access a request if the URL starts with "/static/". |
2 | 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. |
3 | Any URL that starts with "/db/" requires the user to have both been granted the "db" permission as well as be a "ROLE_ADMIN".
You will notice that since we are using the hasRole expression we do not need to specify the "ROLE_" prefix. |
4 | 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. |
Using Path Parameters
Additionally, Spring Security provides a mechanism for discovering path parameters so they can also be accessed in the SpEL expression as well.
For example, you can access a path parameter in your SpEL expression in the following way:
-
Xml
<http>
<intercept-url pattern="/resource/{name}" access="#name == authentication.name"/>
<intercept-url pattern="/**" access="authenticated"/>
</http>
This expression refers to the path variable after /resource/
and requires that it is equal to Authentication#getName
.
Use an Authorization Database, Policy Agent, or Other Service
If you want to configure Spring Security to use a separate service for authorization, you can create your own AuthorizationManager
and match it to anyRequest
.
First, your AuthorizationManager
may look something like this:
-
Java
@Component
public final class OpenPolicyAgentAuthorizationManager implements AuthorizationManager<RequestAuthorizationContext> {
@Override
public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext context) {
// make request to Open Policy Agent
}
}
Then, you can wire it into Spring Security in the following way:
-
Java
@Bean
SecurityFilterChain web(HttpSecurity http, AuthorizationManager<RequestAuthorizationContext> authz) throws Exception {
http
// ...
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().access(authz)
);
return http.build();
}
Favor permitAll
over ignoring
When you have static resources it can be tempting to configure the filter chain to ignore these values.
A more secure approach is to permit them using permitAll
like so:
-
Java
-
Kotlin
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/css/**").permitAll()
.anyRequest().authenticated()
)
http {
authorizeHttpRequests {
authorize("/css/**", permitAll)
authorize(anyRequest, authenticated)
}
}
It’s more secure because even with static resources it’s important to write secure headers, which Spring Security cannot do if the request is ignored.
In this past, this came with a performance tradeoff since the session was consulted by Spring Security on every request.
As of Spring Security 6, however, the session is no longer pinged unless required by the authorization rule.
Because the performance impact is now addressed, Spring Security recommends using at least permitAll
for all requests.
Migrating from authorizeRequests
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
.
Migrating Expressions
Where possible, it is recommended that you use type-safe authorization managers instead of SpEL.
For Java configuration, 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
.
Security 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 |
Further Reading
Now that you have secured your application’s requests, consider securing its methods. You can also read further on testing your application or on integrating Spring Security with other aspects of you application like the data layer or tracing and metrics.