如何用Spring Data JPA和Spring Security实现AuditorAware? [英] How to implement AuditorAware with Spring Data JPA and Spring Security?
问题描述
我们在我们的应用程序中使用Hibernate / JPA,Spring,Spring Data和Spring Security。我有一个使用JPA映射的标准 User
实体。此外,我有一个 UserRepository
public interface UserRepository扩展CrudRepository< User ,Long> {
列表<用户> findByUsername(String username);
}
遵循Spring Data公约命名查询方法。我有一个实体
@Entity
public class Foo extends AbstractAuditable< User,Long> {
私人字符串名称;
}
我想使用Spring Data审计支持。 (如此处所述)因此,我创建了一个 AuditorService
,如下所示:
@Service
public class AuditorService实现AuditorAware< User> {
私人UserRepository userRepository;
@Override
public User getCurrentAuditor(){
String username = SecurityContextHolder.getContext()。getAuthentication()。getName();
列表<用户> users = userRepository.findByUsername(username);
if(users.size()> 0){
return users.get(0);
} else {
throw new IllegalArgumentException();
}
}
@Autowired
public void setUserService(UserService userService){
this.userService = userService;
当我创建一个方法时
@Transactional
public void createFoo(){
Foo bar = new Foo();
fooRepository.save(foo);
}
凡是连线正确且 FooRepository
是一个Spring Data CrudRepository
。然后,由于调用 findByUsername
似乎会触发hibernate将数据刷新到数据库,所以会引发 StackOverflowError
AuditingEntityListener
谁调用 AuditorService#getCurrentAuditor
,这又会触发刷新等等。
如何避免这种递归?加载 User
实体是否存在规范方式?或者有没有办法阻止Hibernate / JPA刷新? 解决方法是不要在 AuditorAware
执行。这会触发描述的循环,因为select查询会触发flush(这是因为Hibernate / JPA想要在执行select之前将数据写入数据库以提交事务),这会触发对 AuditorAware#getCurrentAuditor
。
解决方案是将 User
记录存储在 UserDetails
提供给Spring Security。因此我创建了自己的实现:
pre $ c $ public $ UserAwareUserDetails实现UserDetails {
private final User user;
私人最终收藏<?扩展GrantedAuthority> grantedAuthorities;
$ b $ public UserAwareUserDetails(User user){
this(user,new ArrayList< GrantedAuthority>());
}
public UserAwareUserDetails(User user,Collection< ;? extends GrantedAuthority> grantedAuthorities){
this.user = user;
this.grantedAuthorities = grantedAuthorities;
}
@覆盖
公共收藏<?扩展GrantedAuthority> getAuthorities(){
return grantedAuthorities;
}
@Override
public String getPassword(){
return user.getSaltedPassword();
}
@Override
public String getUsername(){
return user.getUsername();
}
@Override
public boolean isAccountNonExpired(){
return true;
}
@Override
public boolean isAccountNonLocked(){
return true;
}
@Override
public boolean isCredentialsNonExpired(){
return true;
}
@Override
public boolean isEnabled(){
return true;
}
public User getUser(){
return user;
$ / code>
此外,我将 UserDetailsService
加载 User
并创建 UserAwareUserDetails
。现在可以通过 SercurityContextHolder
访问 User
实例:
$ b $ (
We use Hibernate/JPA, Spring, Spring Data and Spring Security in our application. I have a standard User
entity which is mapped using JPA. Further, I have a UserRepository
public interface UserRepository extends CrudRepository<User, Long> {
List<User> findByUsername(String username);
}
which follows the Spring Data convention for naming query methods. I have an entity
@Entity
public class Foo extends AbstractAuditable<User, Long> {
private String name;
}
I want to use Spring Data auditing support. (As descripe here.) Hence I created a AuditorService
as follows:
@Service
public class AuditorService implements AuditorAware<User> {
private UserRepository userRepository;
@Override
public User getCurrentAuditor() {
String username = SecurityContextHolder.getContext().getAuthentication().getName();
List<User> users = userRepository.findByUsername(username);
if (users.size() > 0) {
return users.get(0);
} else {
throw new IllegalArgumentException();
}
}
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
}
When I create a method
@Transactional
public void createFoo() {
Foo bar = new Foo();
fooRepository.save(foo);
}
Where everything is correctly wired and FooRepository
is a Spring Data CrudRepository
. Then a StackOverflowError
is thrown since the the call to findByUsername
seems to trigger hibernate to flush the data to the database which triggers AuditingEntityListener
who calls AuditorService#getCurrentAuditor
which again triggers a flush and so on.
How to avoid this recursion? Is there a "canonical way" to load the User
entity? Or is there a way to prevent Hibernate/JPA from flushing?
The solution is not to fetch the User
record in the AuditorAware
implementation. This triggers the described loop, since a select query triggers a flush (this is the case since Hibernate/JPA wants to write the data to the database to commit the transaction before executing the select), which triggers a call to AuditorAware#getCurrentAuditor
.
The solution is to store the User
record in the UserDetails
provided to Spring Security. Hence I created my own implementation:
public class UserAwareUserDetails implements UserDetails {
private final User user;
private final Collection<? extends GrantedAuthority> grantedAuthorities;
public UserAwareUserDetails(User user) {
this(user, new ArrayList<GrantedAuthority>());
}
public UserAwareUserDetails(User user, Collection<? extends GrantedAuthority> grantedAuthorities) {
this.user = user;
this.grantedAuthorities = grantedAuthorities;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return grantedAuthorities;
}
@Override
public String getPassword() {
return user.getSaltedPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
public User getUser() {
return user;
}
}
Further, I changed my UserDetailsService
to load the User
and create UserAwareUserDetails
. Now it is possible to access the User
instance through the SercurityContextHolder
:
@Override
public User getCurrentAuditor() {
return ((UserAwareUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUser();
}
这篇关于如何用Spring Data JPA和Spring Security实现AuditorAware?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!