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

gre*_*gor 15 spring hibernate jpa spring-security spring-data

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

public interface UserRepository extends CrudRepository<User, Long> {
    List<User> findByUsername(String username);
}
Run Code Online (Sandbox Code Playgroud)

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

@Entity
public class Foo extends AbstractAuditable<User, Long> {
    private String name;
}
Run Code Online (Sandbox Code Playgroud)

我想使用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;
    }
}
Run Code Online (Sandbox Code Playgroud)

当我创建一个方法

@Transactional
public void createFoo() {
    Foo bar = new Foo(); 
    fooRepository.save(foo);
}
Run Code Online (Sandbox Code Playgroud)

所有内容都正确连接并且FooRepository是Spring Data CrudRepository.然后StackOverflowError抛出a ,因为调用findByUsername似乎触发hibernate将数据刷新到数据库,触发AuditingEntityListener谁调用AuditorService#getCurrentAuditor再次触发刷新等等.

如何避免这种递归?加载User实体是否有"规范方式" ?或者有没有办法阻止Hibernate/JPA刷新?

gre*_*gor 14

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

解决方案是将User记录存储在UserDetailsSpring 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

此外,我改变了我UserDetailsService的加载User和创建UserAwareUserDetails.现在可以User通过以下方式访问实例SercurityContextHolder:

@Override
public User getCurrentAuditor() {
    return ((UserAwareUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUser();
}
Run Code Online (Sandbox Code Playgroud)


Ana*_*hah 11

我得到了同样的问题,我所做的只是改变findByUsername(username)方法上的传播Propagation.REQUIRES_NEW,我怀疑这是交易的问题,所以我改为使用新的交易,这对我来说很有效.我希望这可以提供帮助.

@Repository
public interface UserRepository extends JpaRepository<User, String> {

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    List<User> findByUsername(String username);
}
Run Code Online (Sandbox Code Playgroud)

  • 多谢!完美解决方案 (2认同)
  • 很好的解决方案,但是这个 @Transactional 不应该在服务级别吗?它实际上解决了我的问题,只是关于位置的注释:)我将其投入服务并且它起作用了...... (2认同)
  • 惊人的!这有助于创建/更新我的 User 实体,该实体是 AuditorAware 的类型,由于事务的连续回滚而创建 stackoverflow 问题 - 我刚刚在 AudiorAware getCurrentAuditor 使用的存储库方法上添加了此注释 (2认同)