This version is still in development and is not considered stable yet. For the latest stable version, please use Spring Security 6.4.2!

Authorize HttpServletRequests with AuthorizationFilter

This section builds on Servlet Architecture and Implementation by digging deeper into how authorization works within Servlet-based applications.

AuthorizationFilter supersedes FilterSecurityInterceptor. To remain backward compatible, FilterSecurityInterceptor remains the default. This section discusses how AuthorizationFilter works and how to override the default configuration.

The AuthorizationFilter provides authorization for HttpServletRequests. It is inserted into the FilterChainProxy as one of the Security Filters.

You can override the default when you declare a SecurityFilterChain. Instead of using authorizeRequests, use authorizeHttpRequests, like so:

Use authorizeHttpRequests
  • Java

@Bean
SecurityFilterChain web(HttpSecurity http) throws AuthenticationException {
    http
        .authorizeHttpRequests((authorize) -> authorize
            .anyRequest().authenticated();
        )
        // ...

    return http.build();
}

This improves on authorizeRequests in a number of ways:

  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 AuthorizationFilter is used instead of FilterSecurityInterceptor.

authorizationfilter
Figure 1. Authorize HttpServletRequest
  • number 1 First, the AuthorizationFilter obtains an Authentication from the SecurityContextHolder. It wraps this in an Supplier in order to delay lookup.

  • number 2 Second, it passes the Supplier<Authentication> and the HttpServletRequest to the AuthorizationManager.

    • number 3 If authorization is denied, an AccessDeniedException is thrown. In this case the ExceptionTranslationFilter handles the AccessDeniedException.

    • number 4 If access is granted, AuthorizationFilter continues with the FilterChain which allows the application to process normally.

We can configure Spring Security to have different rules by adding more rules in order of precedence.

Authorize Requests
  • Java

@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
	http
		// ...
		.authorizeHttpRequests(authorize -> authorize                                  (1)
			.mvcMatchers("/resources/**", "/signup", "/about").permitAll()         (2)
			.mvcMatchers("/admin/**").hasRole("ADMIN")                             (3)
			.mvcMatchers("/db/**").access((authentication, request) ->
			    Optional.of(hasRole("ADMIN").check(authentication, request))
			        .filter((decision) -> !decision.isGranted())
			        .orElseGet(() -> hasRole("DBA").check(authentication, request));
			)   (4)
			.anyRequest().denyAll()                                                (5)
		);

	return http.build();
}
1 There are multiple authorization rules specified. Each rule is considered in the order they were declared.
2 We specified multiple URL patterns that any user can access. Specifically, any user can access a request if the URL starts with "/resources/", equals "/signup", or equals "/about".
3 Any URL that starts with "/admin/" will be restricted to users who have the role "ROLE_ADMIN". You will notice that since we are invoking the hasRole method we do not need to specify the "ROLE_" prefix.
4 Any URL that starts with "/db/" requires the user to have both "ROLE_ADMIN" and "ROLE_DBA". You will notice that since we are using the hasRole expression we do not need to specify the "ROLE_" prefix.
5 Any URL that has not already been matched on is denied access. This is a good strategy if you do not want to accidentally forget to update your authorization rules.

You can take a bean-based approach by constructing your own RequestMatcherDelegatingAuthorizationManager like so:

Configure RequestMatcherDelegatingAuthorizationManager
  • Java

@Bean
SecurityFilterChain web(HttpSecurity http, AuthorizationManager<RequestAuthorizationContext> access)
        throws AuthenticationException {
    http
        .authorizeHttpRequests((authorize) -> authorize
            .anyRequest().access(access)
        )
        // ...

    return http.build();
}

@Bean
AuthorizationManager<RequestAuthorizationContext> requestMatcherAuthorizationManager(HandlerMappingIntrospector introspector) {
    RequestMatcher permitAll =
            new AndRequestMatcher(
                    new MvcRequestMatcher(introspector, "/resources/**"),
                    new MvcRequestMatcher(introspector, "/signup"),
                    new MvcRequestMatcher(introspector, "/about"));
    RequestMatcher admin = new MvcRequestMatcher(introspector, "/admin/**");
    RequestMatcher db = new MvcRequestMatcher(introspector, "/db/**");
    RequestMatcher any = AnyRequestMatcher.INSTANCE;
    AuthorizationManager<HttpRequestServlet> manager = RequestMatcherDelegatingAuthorizationManager.builder()
            .add(permitAll, (context) -> new AuthorizationDecision(true))
            .add(admin, AuthorityAuthorizationManager.hasRole("ADMIN"))
            .add(db, AuthorityAuthorizationManager.hasRole("DBA"))
            .add(any, new AuthenticatedAuthorizationManager())
            .build();
    return (context) -> manager.check(context.getRequest());
}

You can also wire your own custom authorization managers for any request matcher.

Here is an example of mapping a custom authorization manager to the my/authorized/endpoint:

Custom Authorization Manager
  • Java

@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests((authorize) -> authorize
            .mvcMatchers("/my/authorized/endpoint").access(new CustomAuthorizationManager());
        )
        // ...

    return http.build();
}

Or you can provide it for all requests as seen below:

Custom Authorization Manager for All Requests
  • Java

@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests((authorize) -> authorize
            .anyRequest.access(new CustomAuthorizationManager());
        )
        // ...

    return http.build();
}

By default, the AuthorizationFilter does not apply to DispatcherType.ERROR and DispatcherType.ASYNC. We can configure Spring Security to apply the authorization rules to all dispatcher types by using the shouldFilterAllDispatcherTypes method:

Set shouldFilterAllDispatcherTypes to true
  • Java

  • Kotlin

@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests((authorize) -> authorize
            .shouldFilterAllDispatcherTypes(true)
            .anyRequest.authenticated()
        )
        // ...

    return http.build();
}
@Bean
open fun web(http: HttpSecurity): SecurityFilterChain {
    http {
        authorizeHttpRequests {
            shouldFilterAllDispatcherTypes = true
            authorize(anyRequest, authenticated)
        }
    }
    return http.build()
}