带有 spring-security AuditorAware 的 spring-data-jpa 应用程序中的 ***Exception

Posted

技术标签:

【中文标题】带有 spring-security AuditorAware 的 spring-data-jpa 应用程序中的 ***Exception【英文标题】:***Exception in spring-data-jpa app with spring-security AuditorAware 【发布时间】:2017-07-08 01:03:24 【问题描述】:

我的 Spring 后端有一个非常讨厌的 ***Exception,我需要帮助。这不会轻易解决。我真的希望能在这里找到一些帮助。

我的大部分后端工作。我可以在我的 REST 接口中查询模型,它们很好地由 spring-hateoas、GET、PUT 和 POST 操作返回。但有一个例外:当我尝试更新现有的 DelegationModel 时,我遇到了无休止的 ***Exception。

这是我的DelegetionModel.java 课程。请注意,该委托模型实际上没有任何带有 @CreatedBy 注释的属性!

@Entity
@Data
@NoArgsConstructor
@RequiredArgsConstructor(suppressConstructorProperties = true)  //BUGFIX:  https://jira.spring.io/browse/DATAREST-884
@EntityListeners(AuditingEntityListener.class)  // this is necessary so that UpdatedAt and CreatedAt are handled.
@Table(name = "delegations")
public class DelegationModel 
  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  public Long id;

  /** Area that this delegation is in */
  @NonNull
  @NotNull
  @ManyToOne
  public AreaModel area;

  /** reference to delegee that delegated his vote */
  @NonNull
  @NotNull
  @ManyToOne
  public UserModel fromUser;

  /** reference to proxy that receives the delegation */
  @NonNull
  @NotNull
  @ManyToOne
  public UserModel toProxy;

  @CreatedDate
  @NotNull
  public Date createdAt = new Date();

  @LastModifiedDate
  @NotNull
  public Date updatedAt = new Date();


如Spring-data-jpa doc 中所述,我实现了必要的 AuditorAware 接口,该接口从 SQL DB 加载 UserModel。我原以为只有字段用@CreatedBy 注释的模型才会调用此 AuditorAware 接口。

@Component
public class LiquidoAuditorAware implements AuditorAware<UserModel> 
  Logger log = LoggerFactory.getLogger(this.getClass());  // Simple Logging Facade 4 Java

  @Autowired
  UserRepo userRepo;    

  @Override
  public UserModel getCurrentAuditor() 
      Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
      if (authentication == null || !authentication.isAuthenticated()) 
        log.warn("Cannot getCurrentAuditor. No one is currently authenticated");
        return null;
      
      User principal = (org.springframework.security.core.userdetails.User) authentication.getPrincipal();
      UserModel currentlyLoggedInUser = userRepo.findByEmail(principal.getUsername());   // <<<<======= (!)
      return currentlyLoggedInUser;
     catch (Exception e) 
      log.error("Cannot getCurrentAuditor: "+e);
      return null;
    
  

现在我在UserRestController 中更新了一个DelegationModel。这里的功能性“Scrum 用户故事”是:

作为用户,我希望能够存储一个委托,以便我可以将我的投票权转发给我的代理。

@RestController
@RequestMapping("/liquido/v2/users")
public class UserRestController 

  [...]

  @RequestMapping(value = "/saveProxy", method = PUT, consumes="application/json")
  @ResponseStatus(HttpStatus.CREATED)
  public @ResponseBody String saveProxy(
                      @RequestBody Resource<DelegationModel> delegationResource,
                      //PersistentEntityResourceAssembler resourceAssembler,
                      Principal principal)            throws BindException
  
    [...]
    DelegationModel result = delegationRepo.save(existingDelegation);
    [...]
  

  [...]         


由于某种原因,我看不到,这实际上调用了上面的 AuditorAware 实现。现在的问题是,我的 LqiuidoAuditorAware 实现在无限循环中被一次又一次地调用。似乎 LiquidoAuditorAware.java 中对 UserModel 的查询再次调用了 LiquidoAuditorAware。 (这很不寻常,因为这只是从数据库读取操作。)

这里是full ThreadDump as a Gist

所有代码都可以在github repo中找到

我真的很想在这里得到任何帮助。我在黑暗中寻找:-)

【问题讨论】:

【参考方案1】:

您看到该行为的原因是 AuditorAware 实现是从 JPA @PrePersist/@PreUpdate 回调中调用的。您现在通过调用findByEmail(…) 发出查询,这会再次触发脏检测,进而触发刷新,从而调用回调。

推荐的解决方法是在 Spring Security User 实现中保留 UserModel 的实例(通过在 UserDetailsService 在身份验证时查找实例时查找它),这样您就不需要额外的数据库查询。

另一个(不太推荐的)解决方法可能是将EntityManager 注入AuditorAware 实现,在查询执行之前调用setFlushMode(FlushModeType.COMMIT),然后将其重置为FlushModeType.AUTO,这样就不会触发刷新用于查询执行。

【讨论】:

非常感谢您的快速回答!我很高兴我最初的猜测是正确的,即 AuditorAware 中的查询是问题所在。但我没有看到解决方法。我会这样做的。 仅供参考:一分钟前刚刚发现了这个非常相似的问题:***.com/questions/14223649/… Oliver,这仍然是推荐的方法,还是已在更高版本的 spring-security 或 spring-data-jpa 中修复?

以上是关于带有 spring-security AuditorAware 的 spring-data-jpa 应用程序中的 ***Exception的主要内容,如果未能解决你的问题,请参考以下文章

带有 spring-security 的 OAuth2 - 通过 HTTP 方法限制 REST 访问

Spring-security /login 重定向

带有 spring-security AuditorAware 的 spring-data-jpa 应用程序中的 ***Exception

grails spring-security 插件保护所有控制器,而不仅仅是带有@Secured注解的控制器

Spring-Security:认证后调用方法

Spring-security、Tomcat 和 SPNEGO - 最佳方法