Computer scienceBackendSpring BootSpring SecuritySpring Security internals

UserDetails and GrantedAuthority

11 minutes read

Spring Security framework offers a comprehensive authentication and authorization solution for multi-user applications. In this topic, you will take a closer look at core interfaces that you can use to store user information as well as their implementations provided by the framework.

GrantedAuthority interface

The GrantedAuthority interface represents an authority, a privilege granted to an authenticated user. Essentially, it defines what actions the user can perform. Spring Security uses these authorities to conduct authorization. When a user has the necessary authority, they receive authorization. Otherwise, the framework denies access and generates the AccessDeniedException. In addition to authorities, users may possess roles. In Spring Security, the concepts of roles and authorities are often used interchangeably. However, there is a subtle difference between the two: authorities are fine-grained access control features and represent individual permissions, while roles are typically used to group a set of authorities and are prefixed with ROLE_. For example, a role like "ROLE_ADMIN" might imply a set of authorities, such as "read", "write", and "delete".

Generally, an implementation of GrantedAuthority must be able to represent itself as a String.

Here's the GrantedAuthority interface:

package org.springframework.security.core;

public interface GrantedAuthority extends Serializable {
    String getAuthority();
}

This interface has a single method that returns a String representation of the authority. Spring Security provides a concrete implementation of GrantedAuthority, the SimpleGrantedAuthority class. It serves as a wrapper of a String representation of an authority. To create an instance of SimpleGrantedAuthority, you must pass a non-null, non-blank String to the class constructor:

Java
GrantedAuthority userRole = new SimpleGrantedAuthority("ROLE_USER");
GrantedAuthority updateReportAuthority = new SimpleGrantedAuthority("report.update");
Kotlin
val userRole: GrantedAuthority = SimpleGrantedAuthority("ROLE_USER")
val updateReportAuthority: GrantedAuthority = SimpleGrantedAuthority("report.update")

The getAuthority method of these instances will return "ROLE_USER" and "report.update" respectively. Of course, you can create your own implementation of the GrantedAuthority interface if your use case requires it.

UserDetails interface

In Spring Security, UserDetails is an interface that provides necessary user information for the security framework. It includes details such as username, password, and granted authorities, along with account status information. This includes information on whether the account is enabled, the account is locked, credentials are expired, or the account is expired. This interface is typically useful when you need to load user-specific data during the authentication process.

Here's the UserDetails interface:

package org.springframework.security.core.userdetails;

public interface UserDetails extends Serializable {

    Collection<? extends GrantedAuthority> getAuthorities();

    String getPassword();

    String getUsername();

    boolean isAccountNonExpired();

    boolean isAccountNonLocked();

    boolean isCredentialsNonExpired();

    boolean isEnabled();
}

Each method of the interface serves a specific purpose:

  • getAuthorities(): Returns the authorities granted to the user. This is used to perform role checks. This method must not return null.

  • getPassword(): Returns the password used to authenticate the user. Never store passwords in plain text; always use best practices to secure user passwords.

  • getUsername(): Returns the username used to authenticate the user. It must be a unique String that uniquely identifies the user. Cannot be null.

  • isAccountNonExpired(): Indicates whether the user's account has expired. If this method returns false, the framework will throw the AccountExpiredException if the user attempts to authenticate.

  • isAccountNonLocked(): Indicates whether the user is locked or unlocked. If this method returns false, the LockedException is thrown when the user attempts to authenticate.

  • isCredentialsNonExpired(): Indicates whether the user's credentials (e.g. password) have expired. If this method returns false, the CredentialsExpiredException is thrown during the authentication process.

  • isEnabled(): Indicates whether the user is enabled or disabled. Similarly, if this method returns false, the DisabledException is thrown by Spring Security.

For successful authentication, all these methods must return true. They can be used to implement comprehensive user account management. For example, enabling an account upon registration confirmation, blocking users under certain conditions, forcing users to change passwords regularly, and other scenarios.

User class

While you can always create your own UserDetails implementation, Spring Security provides the User class that implements the UserDetails interface. This class is suitable for many scenarios. It has two constructors. The first one accepts a username, a password, and a collection of GrantedAuthority as arguments. It creates an instance of UserDetails where all the methods related to user-account return true. This is useful if your application does not have user account management functionality. The second constructor accepts four boolean arguments in addition to username, password, and authorities, and allows for customizing the user account status:

Java
User alwaysActiveUser = new User("username", "password", List.of());
User disabledUser = new User("username", "password", false, true, true, true, List.of());
Kotlin
val alwaysActiveUser: UserDetails = User("username", "password", emptyList())
val disabledUser: UserDetails = User("username", "password", false, true, true, true, emptyList())

The User class also provides a builder to create instances in a more readable and flexible way. Here's an example that shows the builder method in use:

Java
UserDetails userDetails = User.builder()
        .username("username")
        .password("password")
        .roles("USER")
        .disabled(true)
        .accountExpired(false)
        .accountLocked(false)
        .credentialsExpired(false)
        .build();
Kotlin
val userDetails: UserDetails = User.builder()
    .username("username")
    .password("password")
    .roles("USER")
    .accountExpired(false)
    .accountLocked(false)
    .credentialsExpired(false)
    .build()

This example shows a UserDetails instance with the given username and password, the role "ROLE_USER", and with a disabled account. If you are using the builder, all methods except for those setting a username and a password are optional, and default values will be set if you don't specify them explicitly.

The demo examples use plain text passwords, which implies that the NoOpPasswordEncoder is used. In real applications, remember to always encode the password with a proper PasswordEncoder implementation before persisting it.

Domain user and UserDetails

The UserDetails interface stores user information required by Spring Security to perform authentication and authorization. In your application, the domain model of a user may contain other domain-specific information, such as a job title for an employee or a list of followed profiles for a social network user. In such cases, the User class provided by Spring Security is not enough. You will have to decide how you are going to model your classes.

There are several options. First, you can make your domain class implement the UserDetails interface:

Java
public class UserProfile implements UserDetails {
    // existing UserProfile fields and methods

    // Implement UserDetails methods
}
Kotlin
class UserProfile : UserDetails {
    // existing UserProfile fields and methods

    // Implement UserDetails methods
}

This is probably the simplest option, but it somewhat violates the single responsibility principle. In this scenario, your class has two responsibilities and contains both the domain user details and the security user details. Sometimes it can be acceptable, but sometimes not.

As a better alternative, you can create a separate class, say SecurityUser , that implements UserDetails and holds a reference to a UserProfile instance. This class would delegate to UserProfile where appropriate and implement the methods specific to UserDetails separately. This approach keeps the security-specific logic separate from your domain model.

Java
public class SecurityUser implements UserDetails {
    private final UserProfile userProfile;

    // implement UserDetails methods, delegating to userProfile where appropriate
}
Kotlin
class SecurityUser(private val userProfile: UserProfile) {

    // implement UserDetails methods, delegating to userProfile where appropriate
}

Another common approach is to load the UserProfile from the database and then convert it into a UserDetails object. This is typically done inside the UserDetailsService.

Conclusion

In this topic, you looked into the purpose of core interfaces of the Spring Security framework: UserDetails and GrantedAuthority. UserDetails stores user-specific information, while GrantedAuthority represents the authorities granted to a user, defining their permissions within the application. We also mentioned the User class and its utility methods and discussed options to separate domain-specific and security-specific information in applications.

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