如何用Spring Data JPA和Spring Security实现AuditorAware? [英] How to implement AuditorAware with Spring Data JPA and Spring Security?

查看:947
本文介绍了如何用Spring Data JPA和Spring Security实现AuditorAware?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们在我们的应用程序中使用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>

此外,我将 UserDetailsS​​ervice 加载 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆