【问题标题】:How to implement AuditorAware with Spring Data JPA and Spring Security?如何使用 Spring Data JPA 和 Spring Security 实现 AuditorAware?
【发布时间】:2012-12-22 19:36:58
【问题描述】:

我们在应用程序中使用 Hibernate/JPA、Spring、Spring Data 和 Spring Security。我有一个使用 JPA 映射的标准 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 审计支持。 (如描述here。)因此我创建了一个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,因为对findByUsername 的调用似乎触发休眠以将数据刷新到数据库,这触发AuditingEntityListener 调用AuditorService#getCurrentAuditor 再次触发刷新等等。

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

【问题讨论】:

    标签: spring hibernate jpa spring-security spring-data


    【解决方案1】:

    我遇到了同样的问题,我所做的只是将 findByUsername(username) 方法上的传播更改为 Propagation.REQUIRES_NEW,我怀疑这是事务的问题,所以我改用新事务并且效果很好为了我。我希望这会有所帮助。

    @Repository
    public interface UserRepository extends JpaRepository<User, String> {
    
        @Transactional(propagation = Propagation.REQUIRES_NEW)
        List<User> findByUsername(String username);
    }
    

    【讨论】:

    • 非常感谢!完美解决方案
    • 很好的解决方案,但是这个@Transactional 不应该在服务级别上吗?它实际上解决了我的问题,只是一个关于地点的说明:) 我把它放在了服务上,它工作了......
    • 太棒了!这有助于创建/更新我的用户实体,它是 AuditorAware 的类型,由于事务的连续回滚而创建 stackoverflow 问题 - 我刚刚在 AudiorAware getCurrentAuditor 正在使用的存储库方法上添加了这个注释
    【解决方案2】:

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

    解决方案是将User记录存储在提供给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。现在可以通过SercurityContextHolder访问User实例了:

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

    【讨论】:

    • 我更改了我的 UserDetailsS​​ervice 以返回一个 UserAwareUserDetails 用户,但是当我执行 getPrincipal 时,对象始终是字符串而不是用户。
    • @user1007522,@gregor 所指的User 不是这个用户org.springframework.security.core.userdetails.User,而是为保存在数据库中而创建的User。所以 db-User 是你应该保存在UserAwareUserDetails 中的那个。
    【解决方案3】:

    看起来您将用户实体用于两件不同的事情:

    • 身份验证
    • 审核

    我认为为审计目的准备一个特殊的 AuditableUser 会更好(它将具有与原始用户相同的用户名字段)。 考虑以下情况:您想从数据库中删除一些用户。如果您的所有审计对象都链接到用户,那么它们将 a)松散作者 b)也可能被级联删除(取决于链接的实现方式)。不确定你想要它。 因此,通过使用特殊的 AuditableUser,您将拥有:

    • 没有递归
    • 能够从系统中删除某些用户并保留有关它的所有审核信息

    【讨论】:

    • 为什么要删除用户?为什么不标记为已删除或某事。
    【解决方案4】:

    说实话,您实际上并不需要另一个实体。 例如,我遇到了类似的问题,我通过以下方式解决了它:

    public class SpringSecurityAuditorAware implements AuditorAware<SUser>, ApplicationListener<ContextRefreshedEvent> {
        private static final Logger LOGGER = getLogger(SpringSecurityAuditorAware.class);
        @Autowired
        SUserRepository repository;
        private SUser systemUser;
    
        @Override
        public SUser getCurrentAuditor() {
            final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            SUser principal;
            if (authentication == null || !authentication.isAuthenticated()) {
                principal = systemUser;
            } else {
                principal = (SUser) authentication.getPrincipal();
            }
            LOGGER.info(String.format("Current auditor is >>> %s", principal));
            return principal;
        }
    
        @Override
        public void onApplicationEvent(final ContextRefreshedEvent event) {
            if (this.systemUser == null) {
                LOGGER.info("%s >>> loading system user");
                systemUser = this.repository.findOne(QSUser.sUser.credentials.login.eq("SYSTEM"));
            }
        }
    }
    

    其中 SUser 是我用于审计和安全的类。 我的用例可能与您的不同,之后我的方法将被删除,但可以这样解决。

    【讨论】:

      猜你喜欢
      • 2017-07-08
      • 2014-10-17
      • 1970-01-01
      • 2014-07-02
      • 1970-01-01
      • 2016-08-23
      • 2016-09-18
      • 2016-01-12
      • 2018-06-11
      相关资源
      最近更新 更多