Spring Security MVC is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications.
In a nutshell, Spring Security is injected into the filter chain of the Java Servlet API.
The entry point to the Spring Security is the DelegatingFilterProxy class which is bascially an implementation of the javax.servlet.Filter interface, and which delegates the filtering to a Spring filter chain via a FilterChainProxy. Spring Security filter chain is by default represented by the DefaultSecurityFilterChain class.
Reference:
org.springframework.security.web.FilterChainProxy org.springframework.security.web.DefaultSecurityFilterChain
Lets how the default security chain looks like.
In the code below we have a basic Spring Security configuration with only the @EnableWebSecurity
annotation.
@Configuration@EnableWebSecuritypublic class SecurityConfig {}
The above configuration results in the following filter chain:
As you can see, by default Spring Security provides:
UsernamePasswordAuthenticationFilter
- responsible for authenticating the user based on the username and password provided in the request.BasicAuthenticationFilter
- responsible for authenticating the user based on the basic authentication mechanism.AuthorizationFilter
- main class that is responsible for make a authorization decision for a given request.The final decision about whether a request is allowed or denied is made by the AuthorizationFilter
class, which is
the last filter in the Spring Security filter chain.
Authorization decision is made for a Authentication
object, which is stored in the SecurityContextHolder
class.
Reference:
org.springframework.security.web.access.intercept.AuthorizationFilter
SecurityContextHolder is a class which stores the details of the currently authenticated user, as the Spring Security documentation states:
The SecurityContextHolder is where Spring Security stores the details of who is authenticated. Spring Security does not care how the SecurityContextHolder is populated. If it contains a value, it is used as the currently authenticated user. The simplest way to indicate a user is authenticated is to set the SecurityContextHolder directly
Source: SecurityContextHolder
Reference: org.springframework.security.core.context.SecurityContextHolder
In order to grant access to a given resource in the AuthorizationFilter
class,
the SecurityContextHolder
class must be populated with the Authentication
object.
For that reason, one of the security filters in the Spring Security filter chain must
set the Authentication
object in the SecurityContextHolder
class.
Let’s have a better look at the AuthorizationFilter
.
It consumes the Authentication
object from the SecurityContextHolder
and delegates it to RequestMatcherDelegatingAuthorizationManager class,
which produces the final authorization decision.
RequestMatcherDelegatingAuthorizationManager
has a list of mappings that define
which AuthorizationManager
should be used for a given request.
RequestMatcherDelegatingAuthorizationManager
default configuration has one mapping:
AuthorizationManager
implementation set to AuthenticatedAuthorizationManager - grants access to authenticated users.
We can modify the default Spring Security filter chain by providing a custom configuration.
To start with, we need to define a method that returns the SecurityFilterChain
bean.
Let’s start with a simple configuration that returns not configured SecurityFilterChain
bean:
@Configuration@EnableWebSecuritypublic class SecurityConfig {@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {return http.build();}}
The code snippet above results in the following filter chain:
What is worth noting is that the AuthorizationFilter
is not present in the filter chain!
This results in the fact that all requests are allowed by default.
Now, let’s add some basic configuration to restrict access to all requests:
@Configuration@EnableWebSecuritypublic class SecurityConfig {@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {return http.authorizeHttpRequests(auth -> auth.anyRequest().authenticated()).build();}}
As you can see, one new filter has been added to the filter chain - AuthorizationFilter
.
RequestMatcherDelegatingAuthorizationManager
from the AuthorizationFilter
has
the same configuration as the one from the default Spring Security filter chain.
AuthorizationManager
set to AuthenticatedAuthorizationManagerNow, if we try to request the application, we will get a 401 Unauthorized
response:
curl -v localhost:8080http 401 Unauthorized
Now, let’s define some custom filter that will set the Authentication
object in the SecurityContextHolder
class.
We will define a AllowAllFilter
, which will set already authenticated
Authentication
object in the SecurityContextHolder
class:
public class AllowAllFilter extends GenericFilterBean {@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {SecurityContext context = SecurityContextHolder.createEmptyContext();Authentication authentication =new TestingAuthenticationToken("username", "password");authentication.setAuthenticated(true);context.setAuthentication(authentication);SecurityContextHolder.setContext(context);}}
The password and username are not important in this case, as we already set the authenticated
flag to true
.
Now, let’s add the AllowAllFilter
to the Spring Security filter chain.
It’s important to remember that the order of the filters is important, so we
need to define where the AllowAllFilter
should be placed in the filter chain.
In our case, we want to place it just before the AuthorizationFilter
:
@Configuration@EnableWebSecuritypublic class SecurityConfig {@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {return http.authorizeHttpRequests(auth -> auth.anyRequest().authenticated()).addFilterBefore(new AllowAllFilter(), AuthorizationFilter.class).build();}}
Once we debug the application, we can see out filer is set just before the AuthorizationFilter
:
Now, if we request the application, we will get a 200 OK
response:
curl -v localhost:8080http 200 OK
We can even log the security context in the MainController
:
@RestControllerpublic class MainController {@GetMapping("/")public void get() {SecurityContext context = SecurityContextHolder.getContext();System.out.println(context);}}
As you can see, the SecurityContext
is populated with the Authentication
object that we set in the AllowAllFilter
:
SecurityContextImpl [Authentication=TestingAuthenticationToken[Principal=username,Credentials=[PROTECTED],Authenticated=true,Details=null,Granted Authorities=[]]]
Quick Links
Legal Stuff