Loading...

Avoiding Static Access to SecurityContext with @AuthenticationPrincipal in Spring

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:

@Controller
public class UserController {
    @RequestMapping("/user")
    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:

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

@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    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.

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

  @Autowired
  private CustomUserDetailsService userDetailsService;

  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userDetailsService);
  }
}

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. Twice a week!
Top