如何使 AuditorAware 与 Spring Data Mongo Reactive 一起使用

Posted

技术标签:

【中文标题】如何使 AuditorAware 与 Spring Data Mongo Reactive 一起使用【英文标题】:How to make AuditorAware work with Spring Data Mongo Reactive 【发布时间】:2018-06-11 08:42:36 【问题描述】:

Spring Security 5 提供了一个ReactiveSecurityContextHolder to fetch the SecurityContext from a Reactive context,但是当我想实现AuditorAware 并自动获得试听工作时,它不起作用。 目前我找不到AuditorAwareReactive 变体

@Bean
public AuditorAware<Username> auditor() 
    return () -> ReactiveSecurityContextHolder.getContext()
        .map(SecurityContext::getAuthentication)
        .log()
        .filter(a -> a != null && a.isAuthenticated())
        .map(Authentication::getPrincipal)
        .cast(UserDetails.class)
        .map(auth -> new Username(auth.getName()))
        .switchIfEmpty(Mono.empty())
        .blockOptional();

我已经在我的引导Application 类中添加了@EnableMongoAuduting

在 Mongo 文档类上。我添加了试听相关的注释。

@CreatedDate
private LocalDateTime createdDate;

@CreatedBy
private Username author;

当我添加一个帖子时,createdDate 已填充,但作者为空。

"id":"5a49ccdb9222971f40a4ada1","title":"my first post","content":"content of my first post","createdDate":"2018-01-01T13:53:31.234","author":null

完整代码为here,基于Spring Boot 2.0.0.M7。

更新: Spring Boot 2.4.0-M2/Spring Data Common 2.4.0-M2/Spring Data Mongo 3.1.0-M2 包括ReactiveAuditorAware,检查this new sample,注意:使用@EnableReactiveMongoAuditing来激活它。

【问题讨论】:

尚无对反应性使用的审核支持。 【参考方案1】:

已弃用:查看原帖中的更新解决方案

在提供官方 reactive AuditAware 之前,有一个替代方案可以通过 Spring Data Mongo 特定的ReactiveBeforeConvertCallback 来实现这些。

    不要使用@EnableMongoAuditing 实现自己的ReactiveBeforeConvertCallback,这里我为那些需要审计的实体使用PersistentEntity接口。
public class PersistentEntityCallback implements ReactiveBeforeConvertCallback<PersistentEntity> 

    @Override
    public Publisher<PersistentEntity> onBeforeConvert(PersistentEntity entity, String collection) 
        var user = ReactiveSecurityContextHolder.getContext()
                .map(SecurityContext::getAuthentication)
                .filter(it -> it != null && it.isAuthenticated())
                .map(Authentication::getPrincipal)
                .cast(UserDetails.class)
                .map(userDetails -> new Username(userDetails.getUsername()))
                .switchIfEmpty(Mono.empty());

        var currentTime = LocalDateTime.now();

        if (entity.getId() == null) 
            entity.setCreatedDate(currentTime);
        
        entity.setLastModifiedDate(currentTime);

        return user
                .map(u -> 
                            if (entity.getId() == null) 
                                entity.setCreatedBy(u);
                            
                            entity.setLastModifiedBy(u);

                            return entity;
                        
                )
                .defaultIfEmpty(entity);
    

查看完整代码here。

【讨论】:

如果代码支持通过相同的端点和数据库保存方法插入/更新,这可能不起作用。如果有人会发送一个带有 id 的 DTO,该 DTO 将被映射到一个构造的 DOCUMENT 中并且它不存在于数据库中,那么所有依赖于 id 的字段都将为空。 请检查完整的代码,我发布的解决方案不关心您的 DTO,只需跟踪您正在操作的文档。 问题是如果构造的实体已经分配了 ID 但它在数据库中不存在,我猜你的解决方案将不起作用。如果 ID 存在,我在这里缺少一些检查存储库的例程。 你可以添加逻辑来满足你的业务对吧?就个人而言,我一直使用 Spring Data 来生成 ID。或者分享您的可行解决方案作为额外答案,它会帮助其他人。【参考方案2】:

我正在发布另一个解决方案,它使用输入 id 来支持更新操作:

@Component
@RequiredArgsConstructor
public class AuditCallback implements ReactiveBeforeConvertCallback<AuditableEntity> 

    private final ReactiveMongoTemplate mongoTemplate;

    private Mono<?> exists(Object id, Class<?> entityClass) 
        if (id == null) 
            return Mono.empty();
        
        return mongoTemplate.findById(id, entityClass);
    

    @Override
    public Publisher<AuditableEntity> onBeforeConvert(AuditableEntity entity, String collection) 
        var securityContext = ReactiveSecurityContextHolder.getContext();
        return securityContext
                .zipWith(exists(entity.getId(), entity.getClass()))
                .map(tuple2 -> 
                    var auditableEntity = (AuditableEntity) tuple2.getT2();
                    auditableEntity.setLastModifiedBy(tuple2.getT1().getAuthentication().getName());
                    auditableEntity.setLastModifiedDate(Instant.now());
                    return auditableEntity;
                )
                .switchIfEmpty(Mono.zip(securityContext, Mono.just(entity))
                        .map(tuple2 -> 
                            var auditableEntity = (AuditableEntity) tuple2.getT2();
                            String principal = tuple2.getT1().getAuthentication().getName();
                            Instant now = Instant.now();
                            auditableEntity.setLastModifiedBy(principal);
                            auditableEntity.setCreatedBy(principal);
                            auditableEntity.setLastModifiedDate(now);
                            auditableEntity.setCreatedDate(now);
                            return auditableEntity;
                        ));
    

【讨论】:

【参考方案3】:

要填充 createdBy 属性,您需要将您的 auditAware bean 与注释 @EnableMongoAuditing 链接

在你的 MongoConfig 类中,定义你的 bean:

@Bean(name = "auditorAware")
public AuditorAware<String> auditor() 
    ....

并在注释中使用它:

@Configuration
@EnableMongoAuditing(auditorAwareRef="auditorAware")
class MongoConfig 
    ....

【讨论】:

目前还没有对反应性使用的审计支持。目前,审计员从ThreadLocal 中撤出,由于不断的线程切换和延迟执行,这不适用于被动使用。 @RichardSinelle 您是否在 reactive 应用程序中尝试过此审核功能? 我开始实现一个原型,灵感来自您提供的代码,谢谢。该 bean 是一个 SimpleReactiveMongoRepository。我让你了解最新情况

以上是关于如何使 AuditorAware 与 Spring Data Mongo Reactive 一起使用的主要内容,如果未能解决你的问题,请参考以下文章

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

没有为每个用户创建 JPA AuditorAware

Spring:如何使路由与页面上的 URL 保持同步?

如何使用 @ManyToMany 审核 @JoinTable

如何使Lombok + Gson与Spring AOP代理一起工作

如何使骆驼简单表达式与spring xml中的属性占位符一起使用