As you've already learned from the Spring Security architecture topic, the user data is saved in the SecurityContext after authentication. Think of it as a container where you can set and retrieve user data. Today, you'll learn more about the SecurityContext and its aid, the SecurityContextHolder.
SecurityContext
The SecurityContext is an interface that stores information about the current authenticated user, including their principal (username or user object) and their granted authorities (roles and permissions) with the current request. The security context is stored in a SecurityContextHolder.
The SecurityContextHolder is a helper class responsible for managing the SecurityContext built with mainly static methods and configurable strategies to handle SecurityContext. Using SecurityContextHolder you and Spring security can get information about the logged in user.
Here are some key methods of SecurityContextHolder:
getContext(): Retrieves theSecurityContextwith the current request.setContext(SecurityContext context): Sets theSecurityContextfor the current thread.clearContext(): Clears theSecurityContextfor the current thread.
Authentication interface
First we need to talk about the Authentication interface since authentication object is what stored in SecurityContext. Here's the source code:
interface Authentication : Principal, Serializable {
fun getAuthorities(): Collection<GrantedAuthority>
fun getCredentials(): Any
fun getDetails(): Any
fun getPrincipal(): Any
fun isAuthenticated(): Boolean
@Throws(IllegalArgumentException::class)
fun setAuthenticated(isAuthenticated: Boolean)
}It represents the token for an authentication request and once the request has been authenticated, the Authentication will usually be stored in a thread-local SecurityContext.So the purpose of the Authentication interface is to encapsulate information about the authenticated user, offering a standardized structure for essential details related to the authentication process.
This interface has many implementations like:
AnonymousAuthenticationToken: Represents an anonymous authentication.TestingAuthenticationToken: For testing purposes like unit testing.UsernamePasswordAuthenticationToken: Represent authentication with user name and password which could be used for real world application.
Retrieve user info
Here's how you can retrieve user:
val context = SecurityContextHolder.getContext()
val authentication = context.authentication
// Accessing user details
val userDetails = authentication.principal as UserDetailsFirst we got the the current context then the authentication object then the principal that cast to UserDetails.
This way could be used anywhere in the app but it's hard to test. You may explore another options Getting authenticated user details topic for retrieve user information.
Set Authentication object
You can set the SecurityContext with a specific Authentication object at runtime. This can be useful in situations where you want to manually authenticate a user for a specific operation or scenario. Here's an example:
val context = SecurityContextHolder.createEmptyContext() // 1
val authentication = TestingAuthenticationToken("username", "password", "ROLE_USER") // 2
context.authentication = authentication
SecurityContextHolder.setContext(context)In the first line we created an empty
SecurityContextobject. UsingSecurityContextHolder.getContext().setAuthentication(authentication)directly in a multi-threaded environment can lead to race conditions, but creating a newSecurityContextwithSecurityContextHolder.createEmptyContext()and setting theAuthenticationensures each thread has its own context, preventing concurrency issues.This line creates a custom
Authenticationobject calledTestingAuthenticationToken. It's common in testing scenarios.The last two lines is about setting the authentication inside the context and then setting the the context itself inside the
SecurityContextHolder.
We passed "username" for principle inside TestingAuthenticationToken as String instead of UserDetails object and this is okay for testing purposes but in real world scenario, you should pass UserDetails object.
Spring Security doesn't care how the SecurityContextHolder is populated. It could be set during the normal authentication process, or it can be manually populated, as shown in the provided code snippet.
SecurityContextHolder strategies
Here are the main SecurityContextHolder strategies:
MODE_THREADLOCAL:
In this strategy, the SecurityContext is stored in a ThreadLocal. This means that each thread processing a request has its own SecurityContext. It's the default strategy in Spring Security.
Example:
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_THREADLOCAL)In a web application powered by Apache Tomcat as the web container, Tomcat follows the "thread-per-request". The default policy utilizes ThreadLocal, which ensures that security contexts are thread-isolated and can be accessed at any point during the request's lifecycle. This approach guarantees reliable management of security and session information within the scope of a single HTTP request.
MODE_INHERITABLETHREADLOCAL:
Similar to MODE_THREADLOCAL, but the SecurityContext is inherited by child threads spawned by the current thread. This is useful in scenarios where new threads are spawned during the processing of a request.
Example:
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL)MODE_GLOBAL:
In this strategy, a static field in the SecurityContextHolder holds the SecurityContext. This means that all threads in the JVM share the same SecurityContext.
Example:
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_GLOBAL)Conclusion
Understanding the Authentication object, the SecurityContext, and the SecurityContextHolder is pivotal in comprehending the authentication and authorization processes in Spring Security. Each request, facilitated by someone, triggers the need for these components to validate and manage user information, ensuring secure and controlled access to resources within the application.