我们在应用程序中使用Hibernate / JPA,Spring,Spring Data和Spring Security。我有一个User使用JPA映射的标准实体。此外,我有一个UserRepository
User
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如下:
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,再次触发了刷新等。
FooRepository
CrudRepository
StackOverflowError
findByUsername
AuditingEntityListener
AuditorService#getCurrentAuditor
如何避免这种递归?是否有“规范的方式”加载User实体?还是有办法防止Hibernate / JPA刷新?
解决方案是不获取实现中的User记录AuditorAware。这会触发所描述的循环,因为选择查询会触发刷新(这种情况是因为Hibernate / JPA希望在执行选择之前将数据写入数据库以提交事务),从而触发对的调用AuditorAware#getCurrentAuditor。
AuditorAware
AuditorAware#getCurrentAuditor
解决方案是将User记录存储在UserDetails提供给Spring Security的文件中。因此,我创建了自己的实现:
UserDetails
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:
UserDetailsService
UserAwareUserDetails
SercurityContextHolder
@Override public User getCurrentAuditor() { return ((UserAwareUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUser(); }