I'm writing a spring boot application (spring boot 1.2.2, spring 4.1.5), that is essentially a REST API. I've setup spring security to automatically load user, check authorization etc. upon every HTTP request. Users are stored in a database, I use hibernate to access all the data inside this database.
In short, the problem is: I load a user
object from my database using hibernate on every HTTP request during PreFilter. I later receive this very object in my RestController's params, but it's no longer attached to any hibernate session. Because of this, I cannot access any lazy collections inside user object without re-reading it from the database first.
The question is: is there a way to start hibernate session during PreFilter and keep it until HTTP request is complete?
Long version, with code: First of all, I setup spring security so it'll load my User object as a part of authorization:
Security configuration:
@Configuration
@Component
@EnableWebMvcSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
...
}
User details service, which loads users from database:
@Service
@Transactional
public class DomainUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username);
if (user == null)
throw new UsernameNotFoundException("User '" + username + "' not found");
return user;
}
}
I also want to inject User object into controller methods, so I do it like this:
@PreAuthorize("hasAuthority('VIEW_ACCOUNT_INFO')")
@Transactional(readOnly = true)
@RequestMapping(value = "/user/api-keys/list", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public KeysResponse getApiKeys(
@AuthenticationPrincipal User user
) {
return new KeysResponse(user);
}
This works fine. However, if I try to lazy load a collection related to user, I get an exception:
@Table(name = "users")
public class User implements UserDetails {
...
@Column
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<ApiKey> keys;
...
}
user.getKeys(); <-- Exception here
failed to lazily initialize a collection of role: com.sebbia.pushwit.core.domain.User.keys, could not initialize proxy - no Session
This happens because hiberante session was closed after the user object was loaded during PreFilter, and now the user
variable is detached. In order to attach it again, I have to re-load it from the database:
@PreAuthorize("hasAuthority('MANAGE_ACCOUNT_INFO')")
@Transactional(readOnly = false)
@RequestMapping(value = "/user/api-keys/add", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public KeysResponse addApiKey(
@RequestParam(required = true) String name,
@RequestParam(required = true) Role role,
@AuthenticationPrincipal User user
) throws ApiKeyAlreadyExistsException {
// I have to re-attach user object to new session
Session session = (Session) entityManager.getDelegate();
user = (User) session.merge(user);
// Now I can change user or load any of it's collections
...
}
This works, but this seems error prone and unnecessary. I'm essentially loading user
from the database twice. This seems like a waste considering that I've just loaded it in the previous method, DomainUserDetailsService.loadUserByUsername
.
Is there a way to start hibernate session during PreFilter and keep it until HTTP request is complete?