一尘不染

如何使用Spring Data JPA和Spring Security实施AuditorAware?

hibernate

我们在应用程序中使用Hibernate / JPA,Spring,Spring Data和Spring
Security。我有一个User使用JPA映射的标准实体。此外,我有一个UserRepository

public interface UserRepository extends CrudRepository<User, Long> {
    List<User> findByUsername(String username);
}

它遵循Spring Data约定来命名查询方法。我有一个实体

@Entity
public class Foo extends AbstractAuditable<User, Long> {
    private String name;
}

我想使用Spring Data审核支持。(如此处所述。)因此,我创建了一个AuditorService如下:

@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;
    }
}

当我创建一个方法时

@Transactional
public void createFoo() {
    Foo bar = new Foo(); 
    fooRepository.save(foo);
}

一切都正确接线的地方FooRepository是Spring Data
CrudRepository。然后,将StackOverflowError引发a
,因为对的调用findByUsername似乎触发了hibernate,以将数据刷新到数据库,从而触发了AuditingEntityListener谁的调用AuditorService#getCurrentAuditor,再次触发了刷新等。

如何避免这种递归?是否有“规范的方式”加载User实体?还是有办法防止Hibernate / JPA刷新?


阅读 398

收藏
2020-06-20

共1个答案

一尘不染

解决方案是不获取实现中的User记录AuditorAware。这会触发所描述的循环,因为选择查询会触发刷新(这种情况是因为Hibernate /
JPA希望在执行选择之前将数据写入数据库以提交事务),从而触发对的调用AuditorAware#getCurrentAuditor

解决方案是将User记录存储在UserDetails提供给Spring Security的文件中。因此,我创建了自己的实现:

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;
    }
}

此外,我更改UserDetailsService了加载User并创建UserAwareUserDetails。现在可以User通过以下方式访问实例SercurityContextHolder

@Override
public User getCurrentAuditor() {
    return ((UserAwareUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUser();
}
2020-06-20