【问题标题】:How to make AuditorAware work with Spring Data Mongo Reactive如何使 AuditorAware 与 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来激活它。

【问题讨论】:

  • 尚无对反应性使用的审核支持。

标签: spring spring-security spring-data-mongodb project-reactor spring-webflux


【解决方案1】:

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

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

  1. 不要使用@EnableMongoAuditing
  2. 实现自己的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。我让你了解最新情况
    猜你喜欢
    • 2012-12-12
    • 2012-12-22
    • 1970-01-01
    • 2012-05-10
    • 2016-02-21
    • 1970-01-01
    • 2018-11-02
    • 2017-01-13
    • 1970-01-01
    相关资源
    最近更新 更多