1158 lines
41 KiB
Plaintext
1158 lines
41 KiB
Plaintext
[[servlet-authorization-authorizationfilter]]
|
|
= Authorize HttpServletRequests
|
|
:figures: servlet/authorization
|
|
|
|
Spring Security allows you to xref:servlet/authorization/index.adoc[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 xref:servlet/configuration/java.adoc#jc-httpsecurity[an `HttpSecurity` instance], it's necessary to declare your authorization rules.
|
|
|
|
[[activate-request-security]]
|
|
Whenever you have an `HttpSecurity` instance, you should at least do:
|
|
|
|
.Use authorizeHttpRequests
|
|
[tabs]
|
|
======
|
|
Java::
|
|
+
|
|
[source,java,role="primary"]
|
|
----
|
|
http
|
|
.authorizeHttpRequests((authorize) -> authorize
|
|
.anyRequest().authenticated()
|
|
)
|
|
----
|
|
|
|
Kotlin::
|
|
+
|
|
[source,kotlin,role="secondary"]
|
|
----
|
|
http {
|
|
authorizeHttpRequests {
|
|
authorize(anyRequest, authenticated)
|
|
}
|
|
}
|
|
----
|
|
|
|
Xml::
|
|
+
|
|
[source,xml,role="secondary"]
|
|
----
|
|
<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-authorize-requests,migrate it to `authorizeHttpRequests`>>
|
|
* I want to <<request-authorization-architecture,understand how the `AuthorizationFilter` components work>>
|
|
* I want to <<match-requests, match requests>> based on a pattern; specifically <<match-by-regex,regex>>
|
|
* I want to match request, and I map Spring MVC to <<mvc-not-default-servlet, something other than the default servlet>>
|
|
* I want to <<authorize-requests, authorize requests>>
|
|
* I want to <<match-by-custom, match a request programmatically>>
|
|
* I want to <<authorize-requests, authorize a request programmatically>>
|
|
* I want to <<remote-authorization-manager, delegate request authorization>> to a policy agent
|
|
|
|
[[request-authorization-architecture]]
|
|
== Understanding How Request Authorization Components Work
|
|
|
|
[NOTE]
|
|
This section builds on xref:servlet/architecture.adoc#servlet-architecture[Servlet Architecture and Implementation] by digging deeper into how xref:servlet/authorization/index.adoc#servlet-authorization[authorization] works at the request level in Servlet-based applications.
|
|
|
|
.Authorize HttpServletRequest
|
|
image::{figures}/authorizationfilter.png[]
|
|
|
|
* image:{icondir}/number_1.png[] First, the `AuthorizationFilter` constructs a `Supplier` that retrieves an xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[Authentication] from the xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontextholder[SecurityContextHolder].
|
|
* image:{icondir}/number_2.png[] Second, it passes the `Supplier<Authentication>` and the `HttpServletRequest` to the xref:servlet/architecture.adoc#authz-authorization-manager[`AuthorizationManager`].
|
|
The `AuthorizationManager` matches the request to the patterns in `authorizeHttpRequests`, and runs the corresponding rule.
|
|
** image:{icondir}/number_3.png[] If authorization is denied, xref:servlet/authorization/events.adoc[an `AuthorizationDeniedEvent` is published], and an `AccessDeniedException` is thrown.
|
|
In this case the xref:servlet/architecture.adoc#servlet-exceptiontranslationfilter[`ExceptionTranslationFilter`] handles the `AccessDeniedException`.
|
|
** image:{icondir}/number_4.png[] If access is granted, xref:servlet/authorization/events.adoc[an `AuthorizationGrantedEvent` is published] and `AuthorizationFilter` continues with the xref:servlet/architecture.adoc#servlet-filters-review[FilterChain] which allows the application to process normally.
|
|
|
|
=== `AuthorizationFilter` Is Last By Default
|
|
|
|
The `AuthorizationFilter` is last in xref:servlet/architecture.adoc#servlet-filterchain-figure[the Spring Security filter chain] by default.
|
|
This means that Spring Security's xref:servlet/authentication/index.adoc[authentication filters], xref:servlet/exploits/index.adoc[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-framework-reference-url}web.html#spring-web[Spring MVC] endpoints.
|
|
Because they are executed by the {spring-framework-reference-url}web.html#mvc-servlet[`DispatcherServlet`] and this comes after the `AuthorizationFilter`, your endpoints need to be <<authorizing-endpoints,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-framework-reference-url}web.html#spring-web[Spring MVC] can `FORWARD` the request to a view resolver that renders a Thymeleaf template, like so:
|
|
|
|
.Sample Forwarding Spring MVC Controller
|
|
[tabs]
|
|
======
|
|
Java::
|
|
+
|
|
[source,java,role="primary"]
|
|
----
|
|
@Controller
|
|
public class MyController {
|
|
@GetMapping("/endpoint")
|
|
public String endpoint() {
|
|
return "endpoint";
|
|
}
|
|
}
|
|
----
|
|
|
|
Kotlin::
|
|
+
|
|
[source,kotlin,role="secondary"]
|
|
----
|
|
@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 <<match-by-dispatcher-type, permit all `FORWARD` dispatches>>.
|
|
|
|
Another example of this principle is {spring-boot-reference-url}web.html#web.servlet.spring-mvc.error-handling[how Spring Boot handles errors].
|
|
If the container catches an exception, say like the following:
|
|
|
|
.Sample Erroring Spring MVC Controller
|
|
[tabs]
|
|
======
|
|
Java::
|
|
+
|
|
[source,java,role="primary"]
|
|
----
|
|
@Controller
|
|
public class MyController {
|
|
@GetMapping("/endpoint")
|
|
public String endpoint() {
|
|
throw new UnsupportedOperationException("unsupported");
|
|
}
|
|
}
|
|
----
|
|
|
|
Kotlin::
|
|
+
|
|
[source,kotlin,role="secondary"]
|
|
----
|
|
@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 <<match-by-dispatcher-type, permit all `ERROR` dispatches>>.
|
|
|
|
=== `Authentication` Lookup is Deferred
|
|
|
|
Remember that xref:servlet/authorization/architecture.adoc#_the_authorizationmanager[the `AuthorizationManager` API uses a `Supplier<Authentication>`].
|
|
|
|
This matters with `authorizeHttpRequests` when requests are <<authorize-requests,always permitted or always denied>>.
|
|
In those cases, xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[the `Authentication`] is not queried, making for a faster request.
|
|
|
|
[[authorizing-endpoints]]
|
|
== 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:
|
|
|
|
.Authorize an Endpoint
|
|
[tabs]
|
|
======
|
|
Java::
|
|
+
|
|
[source,java,role="primary"]
|
|
----
|
|
@Bean
|
|
public SecurityFilterChain web(HttpSecurity http) throws Exception {
|
|
http
|
|
.authorizeHttpRequests((authorize) -> authorize
|
|
.requestMatchers("/endpoint").hasAuthority("USER")
|
|
.anyRequest().authenticated()
|
|
)
|
|
// ...
|
|
|
|
return http.build();
|
|
}
|
|
----
|
|
|
|
Kotlin::
|
|
+
|
|
[source,kotlin,role="secondary"]
|
|
----
|
|
@Bean
|
|
fun web(http: HttpSecurity): SecurityFilterChain {
|
|
http {
|
|
authorizeHttpRequests {
|
|
authorize("/endpoint", hasAuthority("USER"))
|
|
authorize(anyRequest, authenticated)
|
|
}
|
|
}
|
|
|
|
return http.build()
|
|
}
|
|
----
|
|
|
|
Xml::
|
|
+
|
|
[source,xml,role="secondary"]
|
|
----
|
|
<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 xref:servlet/test/method.adoc#test-method-withmockuser[Security's test support] in the following way:
|
|
|
|
.Test Endpoint Authorization
|
|
[tabs]
|
|
======
|
|
Java::
|
|
+
|
|
[source,java,role="primary"]
|
|
----
|
|
@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());
|
|
}
|
|
----
|
|
======
|
|
|
|
[[match-requests]]
|
|
== Matching Requests
|
|
|
|
Above you've already seen <<authorizing-endpoints, 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: <<match-by-ant,Ant>> (as seen above) and <<match-by-regex,Regular Expressions>>.
|
|
|
|
[[match-by-ant]]
|
|
=== 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:
|
|
|
|
.Match with Ant
|
|
[tabs]
|
|
======
|
|
Java::
|
|
+
|
|
[source,java,role="primary"]
|
|
----
|
|
http
|
|
.authorizeHttpRequests((authorize) -> authorize
|
|
.requestMatchers("/resource/**").hasAuthority("USER")
|
|
.anyRequest().authenticated()
|
|
)
|
|
----
|
|
|
|
Kotlin::
|
|
+
|
|
[source,kotlin,role="secondary"]
|
|
----
|
|
http {
|
|
authorizeHttpRequests {
|
|
authorize("/resource/**", hasAuthority("USER"))
|
|
authorize(anyRequest, authenticated)
|
|
}
|
|
}
|
|
----
|
|
|
|
Xml::
|
|
+
|
|
[source,xml,role="secondary"]
|
|
----
|
|
<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:
|
|
|
|
.Authorize and Extract
|
|
[tabs]
|
|
======
|
|
Java::
|
|
+
|
|
[source,java,role="primary"]
|
|
----
|
|
http
|
|
.authorizeHttpRequests((authorize) -> authorize
|
|
.requestMatchers("/resource/{name}").access(new WebExpressionAuthorizationManager("#name == authentication.name"))
|
|
.anyRequest().authenticated()
|
|
)
|
|
----
|
|
|
|
Kotlin::
|
|
+
|
|
[source,kotlin,role="secondary"]
|
|
----
|
|
http {
|
|
authorizeHttpRequests {
|
|
authorize("/resource/{name}", WebExpressionAuthorizationManager("#name == authentication.name"))
|
|
authorize(anyRequest, authenticated)
|
|
}
|
|
}
|
|
----
|
|
|
|
Xml::
|
|
+
|
|
[source,xml,role="secondary"]
|
|
----
|
|
<http>
|
|
<intercept-url pattern="/resource/{name}" access="#name == authentication.name"/>
|
|
<intercept-url pattern="/**" access="authenticated"/>
|
|
</http>
|
|
----
|
|
======
|
|
|
|
Once authorized, you can test it using xref:servlet/test/method.adoc#test-method-withmockuser[Security's test support] in the following way:
|
|
|
|
.Test Directory Authorization
|
|
[tabs]
|
|
======
|
|
Java::
|
|
+
|
|
[source,java,role="primary"]
|
|
----
|
|
@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());
|
|
}
|
|
----
|
|
======
|
|
|
|
[NOTE]
|
|
Spring Security only matches paths.
|
|
If you want to match query parameters, you will need a custom request matcher.
|
|
|
|
[[match-by-regex]]
|
|
=== 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 {security-api-url}org/springframework/security/web/util/matcher/RegexRequestMatcher.html[`RegexRequestMatcher`] to respect this rule, like so:
|
|
|
|
.Match with Regex
|
|
[tabs]
|
|
======
|
|
Java::
|
|
+
|
|
[source,java,role="primary"]
|
|
----
|
|
http
|
|
.authorizeHttpRequests((authorize) -> authorize
|
|
.requestMatchers(RegexRequestMatcher.regexMatcher("/resource/[A-Za-z0-9]+")).hasAuthority("USER")
|
|
.anyRequest().denyAll()
|
|
)
|
|
----
|
|
|
|
Kotlin::
|
|
+
|
|
[source,kotlin,role="secondary"]
|
|
----
|
|
http {
|
|
authorizeHttpRequests {
|
|
authorize(RegexRequestMatcher.regexMatcher("/resource/[A-Za-z0-9]+"), hasAuthority("USER"))
|
|
authorize(anyRequest, denyAll)
|
|
}
|
|
}
|
|
----
|
|
|
|
Xml::
|
|
+
|
|
[source,xml,role="secondary"]
|
|
----
|
|
<http>
|
|
<intercept-url request-matcher="regex" pattern="/resource/[A-Za-z0-9]+" access="hasAuthority('USER')"/>
|
|
<intercept-url pattern="/**" access="denyAll"/>
|
|
</http>
|
|
----
|
|
======
|
|
|
|
[[match-by-httpmethod]]
|
|
=== 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:
|
|
|
|
.Match by HTTP Method
|
|
[tabs]
|
|
======
|
|
Java::
|
|
+
|
|
[source,java,role="primary"]
|
|
----
|
|
http
|
|
.authorizeHttpRequests((authorize) -> authorize
|
|
.requestMatchers(HttpMethod.GET).hasAuthority("read")
|
|
.requestMatchers(HttpMethod.POST).hasAuthority("write")
|
|
.anyRequest().denyAll()
|
|
)
|
|
----
|
|
|
|
Kotlin::
|
|
+
|
|
[source,kotlin,role="secondary"]
|
|
----
|
|
http {
|
|
authorizeHttpRequests {
|
|
authorize(HttpMethod.GET, hasAuthority("read"))
|
|
authorize(HttpMethod.POST, hasAuthority("write"))
|
|
authorize(anyRequest, denyAll)
|
|
}
|
|
}
|
|
----
|
|
|
|
Xml::
|
|
+
|
|
[source,xml,role="secondary"]
|
|
----
|
|
<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"
|
|
|
|
[TIP]
|
|
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 xref:servlet/test/method.adoc#test-method-withmockuser[Security's test support] in the following way:
|
|
|
|
.Test Http Method Authorization
|
|
[tabs]
|
|
======
|
|
Java::
|
|
+
|
|
[source,java,role="primary"]
|
|
----
|
|
@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());
|
|
}
|
|
----
|
|
======
|
|
|
|
[[match-by-dispatcher-type]]
|
|
=== Matching By Dispatcher Type
|
|
|
|
[NOTE]
|
|
This feature is not currently supported in XML
|
|
|
|
As stated earlier, Spring Security <<_all_dispatches_are_authorized, authorizes all dispatcher types by default>>.
|
|
And even though xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontext[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:
|
|
|
|
.Match by Dispatcher Type
|
|
====
|
|
.Java
|
|
[source,java,role="secondary"]
|
|
----
|
|
http
|
|
.authorizeHttpRequests((authorize) -> authorize
|
|
.dispatcherTypeMatchers(DispatcherType.FORWARD, DispatcherType.ERROR).permitAll()
|
|
.requestMatchers("/endpoint").permitAll()
|
|
.anyRequest().denyAll()
|
|
)
|
|
----
|
|
|
|
.Kotlin
|
|
[source,kotlin,role="secondary"]
|
|
----
|
|
http {
|
|
authorizeHttpRequests {
|
|
authorize(DispatcherTypeRequestMatcher(DispatcherType.FORWARD), permitAll)
|
|
authorize(DispatcherTypeRequestMatcher(DispatcherType.ERROR), permitAll)
|
|
authorize("/endpoint", permitAll)
|
|
authorize(anyRequest, denyAll)
|
|
}
|
|
}
|
|
----
|
|
====
|
|
|
|
[[match-by-mvc]]
|
|
=== 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:
|
|
|
|
.Match by MvcRequestMatcher
|
|
====
|
|
.Java
|
|
[source,java,role="primary"]
|
|
----
|
|
@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();
|
|
}
|
|
----
|
|
|
|
.Kotlin
|
|
[source,kotlin,role="secondary"]
|
|
----
|
|
@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)
|
|
}
|
|
}
|
|
----
|
|
|
|
.Xml
|
|
[source,xml,role="secondary"]
|
|
----
|
|
<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)
|
|
|
|
[[match-by-custom]]
|
|
=== Using a Custom Matcher
|
|
|
|
[NOTE]
|
|
This feature is not currently supported in XML
|
|
|
|
In Java configuration, you can create your own {security-api-url}org/springframework/security/web/util/matcher/RequestMatcher.html[`RequestMatcher`] and supply it to the DSL like so:
|
|
|
|
.Authorize by Dispatcher Type
|
|
====
|
|
.Java
|
|
[source,java,role="secondary"]
|
|
----
|
|
RequestMatcher printview = (request) -> request.getParameter("print") != null;
|
|
http
|
|
.authorizeHttpRequests((authorize) -> authorize
|
|
.requestMatchers(printview).hasAuthority("print")
|
|
.anyRequest().authenticated()
|
|
)
|
|
----
|
|
|
|
.Kotlin
|
|
[source,kotlin,role="secondary"]
|
|
----
|
|
val printview: RequestMatcher = { (request) -> request.getParameter("print") != null }
|
|
http {
|
|
authorizeHttpRequests {
|
|
authorize(printview, hasAuthority("print"))
|
|
authorize(anyRequest, authenticated)
|
|
}
|
|
}
|
|
----
|
|
====
|
|
|
|
[TIP]
|
|
Because {security-api-url}org/springframework/security/web/util/matcher/RequestMatcher.html[`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 xref:servlet/test/method.adoc#test-method-withmockuser[Security's test support] in the following way:
|
|
|
|
.Test Custom Authorization
|
|
[tabs]
|
|
======
|
|
Java::
|
|
+
|
|
[source,java,role="primary"]
|
|
----
|
|
@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());
|
|
}
|
|
----
|
|
======
|
|
|
|
[[authorize-requests]]
|
|
== Authorizing Requests
|
|
|
|
Once a request is matched, you can authorize it in several ways <<match-requests, 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, xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[the `Authentication`] is never retrieved from the session
|
|
* `denyAll` - The request is not allowed under any circumstances; note that in this case, the `Authentication` is never retrieved from the session
|
|
* `hasAuthority` - The request requires that the `Authentication` have xref:servlet/authorization/architecture.adoc#authz-authorities[a `GrantedAuthority`] that matches the given value
|
|
* `hasRole` - A shortcut for `hasAuthority` that prefixes `ROLE_` or whatever is configured as the default prefix
|
|
* `hasAnyAuthority` - The request requires that the `Authentication` have a `GrantedAuthority` that matches any of the given values
|
|
* `hasAnyRole` - A shortcut for `hasAnyAuthority` that prefixes `ROLE_` or whatever is configured as the default prefix
|
|
* `access` - The request uses this custom `AuthorizationManager` 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:
|
|
|
|
.Authorize Requests
|
|
[tabs]
|
|
======
|
|
Java::
|
|
+
|
|
[source,java,role="primary"]
|
|
----
|
|
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-framework-reference-url}web.html#spring-web[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.
|
|
|
|
[[authorization-expressions]]
|
|
== 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]]
|
|
=== 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, xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[the `Authentication`] is never retrieved from the session
|
|
* `denyAll` - The request is not allowed under any circumstances; note that in this case, the `Authentication` is never retrieved from the session
|
|
* `hasAuthority` - The request requires that the `Authentication` have xref:servlet/authorization/architecture.adoc#authz-authorities[a `GrantedAuthority`] that matches the given value
|
|
* `hasRole` - A shortcut for `hasAuthority` that prefixes `ROLE_` or whatever is configured as the default prefix
|
|
* `hasAnyAuthority` - The request requires that the `Authentication` have a `GrantedAuthority` that matches any of the given values
|
|
* `hasAnyRole` - A shortcut for `hasAnyAuthority` that prefixes `ROLE_` or whatever is configured as the default prefix
|
|
* `hasPermission` - A hook into your `PermissionEvaluator` instance for doing object-level authorization
|
|
|
|
And here is a brief look at the most common fields:
|
|
|
|
* `authentication` - The `Authentication` instance associated with this method invocation
|
|
* `principal` - The `Authentication#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:
|
|
|
|
.Authorize Requests Using SpEL
|
|
[tabs]
|
|
======
|
|
Xml::
|
|
+
|
|
[source,java,role="primary"]
|
|
----
|
|
<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]]
|
|
=== 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:
|
|
|
|
.Authorize Request using SpEL path variable
|
|
[tabs]
|
|
======
|
|
Xml::
|
|
+
|
|
[source,xml,role="primary"]
|
|
----
|
|
<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`.
|
|
|
|
[[remote-authorization-manager]]
|
|
=== 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:
|
|
|
|
.Open Policy Agent Authorization Manager
|
|
[tabs]
|
|
======
|
|
Java::
|
|
+
|
|
[source,java,role="primary"]
|
|
----
|
|
@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:
|
|
|
|
.Any Request Goes to Remote Service
|
|
[tabs]
|
|
======
|
|
Java::
|
|
+
|
|
[source,java,role="primary"]
|
|
----
|
|
@Bean
|
|
SecurityFilterChain web(HttpSecurity http, AuthorizationManager<RequestAuthorizationContext> authz) throws Exception {
|
|
http
|
|
// ...
|
|
.authorizeHttpRequests((authorize) -> authorize
|
|
.anyRequest().access(authz)
|
|
);
|
|
|
|
return http.build();
|
|
}
|
|
----
|
|
======
|
|
|
|
[[favor-permitall]]
|
|
=== 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:
|
|
|
|
.Permit Static Resources
|
|
====
|
|
.Java
|
|
[source,java,role="secondary"]
|
|
----
|
|
http
|
|
.authorizeHttpRequests((authorize) -> authorize
|
|
.requestMatchers("/css/**").permitAll()
|
|
.anyRequest().authenticated()
|
|
)
|
|
----
|
|
|
|
.Kotlin
|
|
[source,kotlin,role="secondary"]
|
|
----
|
|
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.
|
|
|
|
[[migrate-authorize-requests]]
|
|
== Migrating from `authorizeRequests`
|
|
|
|
[NOTE]
|
|
`AuthorizationFilter` supersedes {security-api-url}org/springframework/security/web/access/intercept/FilterSecurityInterceptor.html[`FilterSecurityInterceptor`].
|
|
To remain backward compatible, `FilterSecurityInterceptor` remains the default.
|
|
This section discusses how `AuthorizationFilter` works and how to override the default configuration.
|
|
|
|
The {security-api-url}org/springframework/security/web/access/intercept/AuthorizationFilter.html[`AuthorizationFilter`] provides xref:servlet/authorization/index.adoc#servlet-authorization[authorization] for ``HttpServletRequest``s.
|
|
It is inserted into the xref:servlet/architecture.adoc#servlet-filterchainproxy[FilterChainProxy] as one of the xref:servlet/architecture.adoc#servlet-security-filters[Security Filters].
|
|
|
|
You can override the default when you declare a `SecurityFilterChain`.
|
|
Instead of using {security-api-url}org/springframework/security/config/annotation/web/builders/HttpSecurity.html#authorizeRequests()[`authorizeRequests`], use `authorizeHttpRequests`, like so:
|
|
|
|
.Use authorizeHttpRequests
|
|
[tabs]
|
|
======
|
|
Java::
|
|
+
|
|
[source,java,role="primary"]
|
|
----
|
|
@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:
|
|
|
|
1. Uses the simplified `AuthorizationManager` API instead of metadata sources, config attributes, decision managers, and voters.
|
|
This simplifies reuse and customization.
|
|
2. 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.
|
|
3. Bean-based configuration support.
|
|
|
|
When `authorizeHttpRequests` is used instead of `authorizeRequests`, then {security-api-url}org/springframework/security/web/access/intercept/AuthorizationFilter.html[`AuthorizationFilter`] is used instead of {security-api-url}org/springframework/security/web/access/intercept/FilterSecurityInterceptor.html[`FilterSecurityInterceptor`].
|
|
|
|
=== Migrating Expressions
|
|
|
|
Where possible, it is recommended that you use type-safe authorization managers instead of SpEL.
|
|
For Java configuration, {security-api-url}org/springframework/security/web/access/expression/WebExpressionAuthorizationManager.html[`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:
|
|
|
|
[tabs]
|
|
======
|
|
Java::
|
|
+
|
|
[source,java,role="primary"]
|
|
----
|
|
.requestMatchers("/test/**").access(new WebExpressionAuthorizationManager("hasRole('ADMIN') && hasRole('USER')"))
|
|
----
|
|
|
|
Kotlin::
|
|
+
|
|
[source,kotlin,role="secondary"]
|
|
----
|
|
.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:
|
|
|
|
[tabs]
|
|
======
|
|
Java::
|
|
+
|
|
[source,java,role="primary"]
|
|
----
|
|
.requestMatchers("/test/**").access((authentication, context) ->
|
|
new AuthorizationDecision(webSecurity.check(authentication.get(), context.getRequest())))
|
|
----
|
|
|
|
Kotlin::
|
|
+
|
|
[source,kotlin,role="secondary"]
|
|
----
|
|
.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 {security-api-url}org/springframework/security/web/access/expression/DefaultHttpSecurityExpressionHandler.html[`DefaultHttpSecurityExpressionHandler`] with a bean resolver and supply that to `WebExpressionAuthorizationManager#setExpressionhandler`.
|
|
|
|
[[security-matchers]]
|
|
== Security Matchers
|
|
|
|
The {security-api-url}org/springframework/security/web/util/matcher/RequestMatcher.html[`RequestMatcher`] interface is used to determine if a request matches a given rule.
|
|
We use `securityMatchers` to determine if xref:servlet/configuration/java.adoc#jc-httpsecurity[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:
|
|
|
|
[tabs]
|
|
======
|
|
Java::
|
|
+
|
|
[source,java,role="primary"]
|
|
----
|
|
@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();
|
|
}
|
|
}
|
|
----
|
|
|
|
Kotlin::
|
|
+
|
|
[source,kotlin,role="secondary"]
|
|
----
|
|
@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-framework-reference-url}web.html#spring-web[Spring MVC] is in the classpath, then {security-api-url}org/springframework/security/web/servlet/util/matcher/MvcRequestMatcher.html[`MvcRequestMatcher`] will be used, otherwise, {security-api-url}org/springframework/security/web/servlet/util/matcher/AntPathRequestMatcher.html[`AntPathRequestMatcher`] will be used.
|
|
You can read more about the Spring MVC integration xref:servlet/integrations/mvc.adoc[here].
|
|
|
|
If you want to use a specific `RequestMatcher`, just pass an implementation to the `securityMatcher` and/or `requestMatcher` methods:
|
|
|
|
[tabs]
|
|
======
|
|
Java::
|
|
+
|
|
[source,java,role="primary"]
|
|
----
|
|
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) {
|
|
// ...
|
|
}
|
|
}
|
|
----
|
|
|
|
Kotlin::
|
|
+
|
|
[source,kotlin,role="secondary"]
|
|
----
|
|
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 xref:servlet/authorization/method-security.adoc[securing its methods].
|
|
You can also read further on xref:servlet/test/index.adoc[testing your application] or on integrating Spring Security with other aspects of you application like xref:servlet/integrations/data.adoc[the data layer] or xref:servlet/integrations/observability.adoc[tracing and metrics].
|