mirror of
				https://github.com/spring-projects/spring-security.git
				synced 2025-10-26 20:28:44 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1172 lines
		
	
	
		
			41 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			1172 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
 | |
| [.invert-dark]
 | |
| 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}reference/web/servlet.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 javadoc:org.springframework.security.web.util.matcher.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
 | |
| [tabs]
 | |
| ======
 | |
| 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
 | |
| [tabs]
 | |
| ======
 | |
| 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 javadoc:org.springframework.security.web.util.matcher.RequestMatcher[] and supply it to the DSL like so:
 | |
| 
 | |
| .Authorize by Dispatcher Type
 | |
| [tabs]
 | |
| ======
 | |
| 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 javadoc:org.springframework.security.web.util.matcher.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
 | |
| [tabs]
 | |
| ======
 | |
| 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 javadoc:org.springframework.security.web.access.intercept.FilterSecurityInterceptor[].
 | |
| To remain backward compatible, `FilterSecurityInterceptor` remains the default.
 | |
| This section discusses how `AuthorizationFilter` works and how to override the default configuration.
 | |
| 
 | |
| The javadoc:org.springframework.security.web.access.intercept.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 javadoc:org.springframework.security.config.annotation.web.builders.HttpSecurity#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 javadoc:org.springframework.security.web.access.intercept.AuthorizationFilter[] is used instead of javadoc:org.springframework.security.web.access.intercept.FilterSecurityInterceptor[].
 | |
| 
 | |
| === Migrating Expressions
 | |
| 
 | |
| Where possible, it is recommended that you use type-safe authorization managers instead of SpEL.
 | |
| For Java configuration, javadoc:org.springframework.security.web.access.expression.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 javadoc:org.springframework.security.web.access.expression.DefaultHttpSecurityExpressionHandler[] with a bean resolver and supply that to `WebExpressionAuthorizationManager#setExpressionhandler`.
 | |
| 
 | |
| [[security-matchers]]
 | |
| == Security Matchers
 | |
| 
 | |
| The javadoc:org.springframework.security.web.util.matcher.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("/api/user/**").hasRole("USER")   <2>
 | |
| 				.requestMatchers("/api/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("/api/user/**", hasRole("USER"))                       <2>
 | |
|                 authorize("/api/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 `/api/user/` to users with the `USER` role
 | |
| <3> Allow access to URLs that start with `/api/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 javadoc:org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher[] will be used, otherwise, javadoc:org.springframework.security.web.util.matcher.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("/api/user/**")).hasRole("USER")     <3>
 | |
| 				.requestMatchers(regexMatcher("/api/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("/api/user/**"), hasRole("USER"))           <3>
 | |
|                 authorize(regexMatcher("/api/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 `/api/user/` to users with the `USER` role, using `AntPathRequestMatcher`
 | |
| <4> Allow access to URLs that start with `/api/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].
 |