Spring Security: How to handle a User object with lazy-loaded JPA fields during authentication?

137 Views Asked by At

I'm working on a Spring Boot application with spring-data-jpa (hibernate) and spring security. My user object looks like this:

@Entity(name = "User")
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public User implements UserDetails {

   @Id
   @Column(name = "ID", nullable = false, unique = true)
   private UUID id;

   @Column(name = "username", nullable = false, unique = true)
   private String username;

   @Column(name = "passwordHash", nullable = false)
   private String passwordHash;
   
   @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
   @ManyToMany(fetch = FetchType.EAGER)
   @JoinTable(
      name = "User_Roles",
      joinColumns = [JoinColumn(name = "User_ID")],
      inverseJoinColumns = [JoinColumn(name = "Role_ID")]
   )
   private Set<Role> roles;
 
   // constructor, getters, setters
   // hashCode() & equals() based on id
}

This object is fetched in our AuthenticationServletFilter and subsequently stored in the SecurityContext. As you can see, the roles collection is currently fetched in an eager fashion. The Role itself consists of ID and a name.

I'm now faced with the following issue:

  • Since authentication happens on every request, I need to use the Hibernate Second-Level Cache to cache the user objects.
  • Hibernate seems to refuse to do that because of the eagerly loaded roles field. In the debug logs, I can see SQL queries which fetch the user object again and again for every authentication.
  • Loading the roles in a lazy fashion is difficult, because it means that the processing which occurs in the AuthenticationServletFilter would have to be @Transactional. If it's not, any code which accesses user.roles from the SecurityContext later on will receive a LazyInitializationException. But marking a request filter as @Transactional feels wrong from a software architecture point of view.

What's the standard course of action for a situation like this? I could imagine that this is a pretty standard scenario.

I tried to make the roles collection lazy-loaded, but that resulted in LazyInitializationExceptions later down the line. Having it loaded eagerly fixes that, but seems to prevent the hibernate second level cache from caching and/or reusing the user object. Marking the AuthenticationServletFilter as @Transactional feels wrong.

0

There are 0 best solutions below