Computer scienceBackendSpring BootSpring SecuritySpring Security internals

SecurityContext

14 minutes read

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

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:

  1. getContext(): Retrieves the SecurityContext with the current request.

  2. setContext(SecurityContext context): Sets the SecurityContext for the current thread.

  3. clearContext(): Clears the SecurityContext for 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 UserDetails

First 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)
  1. In the first line we created an empty SecurityContext object. Using SecurityContextHolder.getContext().setAuthentication(authentication) directly in a multi-threaded environment can lead to race conditions, but creating a new SecurityContext with SecurityContextHolder.createEmptyContext() and setting the Authentication ensures each thread has its own context, preventing concurrency issues.

  2. This line creates a custom Authentication object called TestingAuthenticationToken. It's common in testing scenarios.

  3. 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.

10 learners liked this piece of theory. 0 didn't like it. What about you?
Report a typo