You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot中如何将当前SecurityContext传递给@RabbitListener?

解决Spring Security Authentication通过RabbitMQ传递的序列化问题

针对你遇到的直接序列化Authentication对象导致Jackson反序列化失败的问题,有两种惯用的解决方案,都无需依赖Spring Integration,完全贴合AMQP的简单实现:


方案一:自定义Auth上下文DTO(推荐,轻量可控)

核心思路是不直接序列化Authentication对象,而是提取审计和权限校验所需的核心信息,封装成普通POJO(DTO)传递,避免Jackson处理复杂的Spring Security内部类。

1. 定义AuthContextDTO

这是一个普通的数据传输对象,只包含你需要的关键信息:

data class AuthContextDTO(
    val userId: String?, // 用于审计或业务逻辑的用户ID
    val username: String, // @LastModifiedBy会读取这个值
    val authorities: List<String>, // 角色/权限列表,用于@PreAuthorize等校验
    val authType: String // 标记认证类型,用于消费者端重建Authentication
)

2. 生产者端:提取信息并序列化DTO到AMQP Header

修改你的AmqpTemplate配置,将Authentication转换为DTO后放入Header:

rabbitTemplate.setBeforePublishPostProcessors(
    MessagePostProcessor { message ->
        val authentication = SecurityContextHolder.getContext().authentication
        val authDTO = when (authentication) {
            is AnonymousAuthenticationToken -> AuthContextDTO(
                userId = null,
                username = authentication.name,
                authorities = authentication.authorities.map { it.authority },
                authType = "ANONYMOUS"
            )
            is OAuth2AuthenticationToken -> AuthContextDTO(
                userId = authentication.principal.attributes["sub"] as? String, // 根据你的OAuth2实际字段调整
                username = authentication.name,
                authorities = authentication.authorities.map { it.authority },
                authType = "OAUTH2"
            )
            else -> throw IllegalArgumentException("Unsupported authentication type: ${authentication.javaClass.name}")
        }
        message.messageProperties.setHeader("x-auth-context", objectMapper.writeValueAsString(authDTO))
        message
    }
)

3. 消费者端:从DTO重建Authentication并设置SecurityContext

在消费者方法中,解析Header里的DTO,然后根据类型重建对应的Authentication实例,同时注意使用finally块清理上下文,避免线程池污染:

@RabbitListener(queues = [TrackingEventPublisher.QUEUE_NAME])
fun handleTrackingMessage(trackingEvent: TrackingEvent, headers: MessageHeaders) {
    val authContextJson = headers["x-auth-context"].toString()
    val authContext = objectMapper.readValue(authContextJson, AuthContextDTO::class.java)

    val authentication = when (authContext.authType) {
        "ANONYMOUS" -> AnonymousAuthenticationToken(
            "anonymous-key", // 和你系统中匿名认证的key保持一致,Spring默认可用"anonymousUser"
            authContext.username,
            authContext.authorities.map { SimpleGrantedAuthority(it) }
        )
        "OAUTH2" -> {
            // 重建OAuth2AuthenticationToken,这里用DefaultOAuth2User作为示例,可替换成你实际的用户类
            val principal = DefaultOAuth2User(
                authContext.authorities.map { SimpleGrantedAuthority(it) },
                mapOf("sub" to authContext.userId, "name" to authContext.username),
                "name"
            )
            OAuth2AuthenticationToken(principal, principal.authorities, "your-oauth2-registration-id") // 替换成你的OAuth2注册ID
        }
        else -> throw IllegalArgumentException("Unsupported auth type: ${authContext.authType}")
    }

    SecurityContextHolder.setContext(
        SecurityContextHolder.createEmptyContext().apply {
            this.authentication = authentication
        }
    )

    try {
        logger.info { "Logging event: ${trackingEvent.uuid} started, auth user: ${authentication.name}" }
        trackingService.save(trackingEvent)
        logger.info { "Logging event: ${trackingEvent.uuid} finished" }
    } finally {
        // 必须清理上下文,防止线程复用导致的安全上下文泄漏
        SecurityContextHolder.clearContext()
    }
}

这个方案的优势:

  • DTO是普通POJO,完全符合Jackson序列化要求,无反序列化风险
  • 只传递必要信息,减少AMQP Header的体积
  • 完全可控,可根据业务需求调整字段

方案二:使用Spring Security Jackson官方模块

如果你不想自己写转换逻辑,可以使用Spring官方提供的spring-security-jackson2模块,专门解决Spring Security对象的序列化问题。

1. 引入依赖

确保依赖版本和你的Spring Security版本一致(Maven示例):

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-jackson2</artifactId>
</dependency>

2. 配置ObjectMapper注册Security模块

@Bean
fun objectMapper(): ObjectMapper {
    return ObjectMapper().apply {
        // 注册Spring Security的Jackson模块
        registerModule(SecurityJackson2Modules.getModules(this::class.java.classLoader))
    }
}

3. 生产者端直接序列化Authentication

rabbitTemplate.setBeforePublishPostProcessors(
    MessagePostProcessor { message ->
        val authentication = SecurityContextHolder.getContext().authentication
        message.messageProperties.setHeader("x-authentication", objectMapper.writeValueAsString(authentication))
        message
    }
)

4. 消费者端反序列化并设置上下文

@RabbitListener(queues = [TrackingEventPublisher.QUEUE_NAME])
fun handleTrackingMessage(trackingEvent: TrackingEvent, headers: MessageHeaders) {
    val authJson = headers["x-authentication"].toString()
    val authentication = objectMapper.readValue(authJson, Authentication::class.java)

    SecurityContextHolder.setContext(
        SecurityContextHolder.createEmptyContext().apply {
            this.authentication = authentication
        }
    )

    try {
        logger.info { "Logging event: ${trackingEvent.uuid} started, auth user: ${authentication.name}" }
        trackingService.save(trackingEvent)
        logger.info { "Logging event: ${trackingEvent.uuid} finished" }
    } finally {
        SecurityContextHolder.clearContext()
    }
}

这个方案的优势:

  • 无需自定义DTO和转换逻辑,官方维护
  • 支持大部分Spring Security内置的Authentication实现

注意事项:

  • 如果有自定义的Authentication实现,需要额外编写Jackson序列化器/反序列化器
  • 序列化后的JSON会包含更多字段,比DTO方案更冗余

内容的提问来源于stack exchange,提问作者JustAnEuropean

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 16:24:52