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

Quarkus同时使用OIDC与JWT认证时JsonWebToken注入为空问题

Quarkus多认证机制问题:OIDC与自签发JWT冲突及注入异常

问题背景

我正在开发一个Quarkus 3.24应用,需要支持两种认证方式:

  • 用户通过OIDC(Keycloak)采用标准授权码流完成认证;
  • 客户端(代理或设备)通过登录时生成的自签发短期JWT完成认证。

参考Quarkus文档中《支持多种HTTP认证机制》的内容后,我实现了一个自定义HttpAuthenticationMechanism,根据自定义请求头HELIX-AGENT选择使用OIDC或JWT认证机制。但实际运行中出现了两个核心问题:

  1. 选择JWT认证后,OIDC仍会触发处理逻辑,产生警告;
  2. 直接注入JsonWebToken得到NullJsonWebToken,但从SecurityIdentity.principal能获取到有效的JWT主体信息。

自定义认证机制实现

@Alternative
@Priority(1)
@ApplicationScoped
class CustomAwareJWTAuthMechanism @Inject constructor(
    private val jwt: JWTAuthMechanism,
    private val oidc: OidcAuthenticationMechanism
) : HttpAuthenticationMechanism {

    companion object {
        private const val HELIX_AGENT_HEADER = "HELIX-AGENT"
    }

    override fun authenticate(
        context: RoutingContext,
        identityProviderManager: IdentityProviderManager
    ): Uni<SecurityIdentity> {
        return selectMechanism(context).authenticate(context, identityProviderManager)
    }

    override fun getChallenge(context: RoutingContext): Uni<ChallengeData> {
        return selectMechanism(context).getChallenge(context)
    }

    override fun getCredentialTypes(): MutableSet<Class<out AuthenticationRequest?>?> {
        return mutableSetOf<Class<out AuthenticationRequest?>>().apply {
            addAll(jwt.credentialTypes)
            addAll(oidc.credentialTypes)
        }
    }

    override fun getCredentialTransport(context: RoutingContext): Uni<HttpCredentialTransport> {
        return selectMechanism(context).getCredentialTransport(context)
    }

    private fun selectMechanism(context: RoutingContext): HttpAuthenticationMechanism {
        return if (context.request().getHeader(HELIX_AGENT_HEADER) != null) jwt else oidc
    }
}

问题详情

JWT生成逻辑

客户端登录时,服务端生成自签发JWT:

@POST
@Path("/logon")
@PermitAll
fun logon(@Valid dto: LogonWorkerRequestDto): WorkerLogonResponseDto {
    val worker = workerService.create(dto)
    val token = Jwt
        .subject(worker.id.toString())
        .preferredUserName(worker.name)
        .groups("worker")
        .sign()
        ?: throw IllegalStateException("Failed to create token")
    return WorkerLogonResponseDto(token)
}

请求异常表现

客户端后续请求API时携带该JWT,并添加HELIX-AGENT头标识身份类型,但出现两个异常:

  1. OIDC不必要触发警告:
    尽管已选择JWT认证机制,OIDC仍会尝试解析JWT,输出警告:
    OidcJsonWebTokenProducer - Identity is not associated with an access token.
    Inject either 'SecurityIdentity' or 'UserInfo' if you need both code and bearer token flows.
    
  2. JsonWebToken注入异常:
    在资源类中注入JsonWebToken得到NullJsonWebToken,但SecurityIdentity.principal包含有效的DefaultJwtCallerPrincipal:
    @Path("/secure")
    class HelloResource @Inject constructor(
        private val identity: SecurityIdentity,
        private val jwt: JsonWebToken,
    ) {
    
        @GET
        @Authenticated
        fun getAuthenticatedUser(): String {
            return "Authenticated user: ${identity.principal.name}, JWT subject: ${jwt.name}"
        }
    }
    
    请求结果:
    GET /secure
    
    Authenticated user: Bruno12311, JWT subject: null
    

核心疑问

  1. 为何自定义机制选择JWT认证后,OIDC仍会尝试处理请求?
  2. 在Quarkus中使用多种认证机制时,这是预期行为吗?
  3. 如何确保自签发JWT仅由SmallRye JWT机制处理,完全不触发OIDC?
  4. 为何JsonWebToken注入得到NullJsonWebToken,但SecurityIdentity.principal却包含有效的DefaultJwtCallerPrincipal?

问题解答

1. OIDC仍触发处理的原因

Quarkus的OIDC扩展默认注册了OidcJsonWebTokenProducer这个CDI生产者,它会尝试在容器中生成JsonWebToken实例,不管当前使用的是哪种认证机制。即使你的自定义认证机制选择了JWT,这个OIDC生产者仍会触发,检查当前身份是否关联OIDC的访问令牌,找不到就输出警告。

2. 是否属于预期行为

这不属于多认证机制的预期行为,但属于OIDC扩展的默认行为。OIDC扩展的CDI生产者不会感知自定义认证机制的选择逻辑,只要OIDC扩展在项目依赖中,它就会自动工作。

3. 确保自签发JWT仅由SmallRye JWT处理的方案

有两种可行方案:

  • 方案一:禁用OIDC的JWT生产者
    在application.properties中添加配置,关闭OIDC的OidcJsonWebTokenProducer:
    quarkus.oidc.json-web-token-producer.enabled=false
    
  • 方案二:精细化路径与令牌判断
    除了检查HELIX-AGENT头,还可以在selectMechanism中添加令牌类型判断(比如根据签名密钥区分自签发JWT和OIDC令牌);同时通过quarkus.oidc.paths配置OIDC仅处理用户登录相关路径,客户端请求的路径由JWT机制单独处理。

4. JsonWebToken注入异常的原因

JsonWebToken存在两个CDI生产者:SmallRye JWT的JwtProducer和OIDC的OidcJsonWebTokenProducer。当OIDC的生产者优先级更高时,它会先被触发,发现当前身份不是OIDC生成的,就返回NullJsonWebToken,并被注入到字段中。而SecurityIdentity是由你选择的JWT认证机制正确生成的,因此其principal是有效的DefaultJwtCallerPrincipal。解决这个问题的关键就是关闭OIDC的JWT生产者(方案一),让SmallRye JWT的生产者正确生成实例。


内容的提问来源于stack exchange,提问作者Artur K.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 14:57:02