带有 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 AuditorAware 的 spring-data-jpa 应用程序中的 ***Exception