Avoiding Static Access to SecurityContext with @AuthenticationPrincipal in Spring

August 16, 2023
3 minutes to read
Share this post:

In almost every web application, logic related to the user must be executed. To access this, his data is assigned based on his authentication. With Spring Security, the authentication framework in the Spring stack, we have various ways to accomplish this essential task.

Static Access and its Drawbacks

In many applications and examples, it is common to statically retrieve the SecurityContext in Spring to access the current UserDetails. However, this has some disadvantages:

  • Coupling: Static access leads to tight coupling between the code and the Spring Security Framework. Tests become more difficult, and the code design suffers as a result.
  • Thread Safety: Since the information is stored in the thread, there is a risk that it will be overwritten in a multithreaded environment.
  • Casting: It is necessary to cast the implementation at runtime. This means potential errors are not detected by the compiler.
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
UserDetails user = (UserDetails) authentication.getPrincipal();

The Elegant Way: @AuthenticationPrincipal

Spring offers a better solution, allowing you to avoid a close connection between your controller class and the SecurityContext. The @AuthenticationPrincipal annotation helps you type-safely retrieve your UserDetails implementation. It looks like this:

public class UserController {
    public String getUser(@AuthenticationPrincipal User user) {
        // ...

When can I use @AuthenticationPrincipal?

To successfully use the @AuthenticationPrincipal annotation, you must implement the UserDetails interface and register it in Spring Security. Here are the steps you need to follow:

1. Implementing the UserDetails Interface

The UserPrincipal class must implement the UserDetails interface. This ensures that Spring Security correctly recognizes the class and retrieves the authenticated user details correctly.

If you use JPA for persistence, you could model your User class as an entity:

public class User implements UserDetails {
    private UUID id = UUID.randomUUID();
    private String username;
    private String password;
    private String role;
    private boolean enabled;
    // Implement the methods of the UserDetails interface
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return Collections.singletonList(() -> role);
    public boolean isAccountNonExpired() {
        return true;
    public boolean isAccountNonLocked() {
        return true;
    public boolean isCredentialsNonExpired() {
        return true;
    public boolean isEnabled() {
        return enabled;
    public String getUsername() {
        return username;
    public String getPassword() {
        return password;

2. Configuration of a Custom UserDetailsService

Spring Security uses a UserDetailsService to instantiate the UserDetails class. You can create your own service that implements the UserDetailsService interface, and configure it as a bean.

public class CustomUserDetailsService implements UserDetailsService {

    private UserRepository userRepository;

    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return userRepository.findByUsername(username)
                .orElseThrow(() -> new UsernameNotFoundException("User not found"));

3. Integration with Spring Security Configuration

Finally, you must inform Spring Security to use your CustomUserDetailsService. To do this, you implement WebSecurityConfigurerAdapter and register your UserDetailsService in the configure() method.

public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

  private CustomUserDetailsService userDetailsService;

  protected void configure(AuthenticationManagerBuilder auth) throws Exception {

Why This Approach is Better

With this approach:

  • You get easy access to user information without directly using the SecurityContext.
  • You increase the testability of your code, as you can mock the controller methods more easily.
  • You adhere to the Single Responsibility Principle and avoid casting in the API layer.

Have you heard of Marcus' Backend Newsletter?

New ideas. Every week!