Quarkus集成SmallRye JWT时HS256签名验证失败排查
问题现象
Quarkus应用不生成JWT令牌,但持有HS256签名用的密钥qwertyuiopasdfghjklzxcvbnm123456,验证请求中的令牌时始终报错:
io.smallrye.jwt.auth.principal.ParseException: SRJWT07000: Failed to verify a token
...
Caused by: org.jose4j.jwt.consumer.InvalidJwtSignatureException: JWT rejected due to invalid signature. Additional details: [[9] Invalid JWS Signature: JsonWebSignature{"typ":"JWT","alg":"HS256"}->eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE2NjczODI2NzIsImV4cCI6MTY5ODkxODY3MiwiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIkdpdmVuTmFtZSI6IkpvaG5ueSIsIlN1cm5hbWUiOiJSb2NrZXQiLCJFbWFpbCI6Impyb2NrZXRAZXhhbXBsZS5jb20iLCJSb2xlIjpbIk1hbmFnZXIiLCJQcm9qZWN0IEFkbWluaXN0cmF0b3IiXX0.5vBHzbTKjLnAkAIYuA3c50nWV--o9jIWV2i0GZI-aw4]
当前配置
application.properties
smallrye.jwt.verify.key-format=JWK smallrye.jwt.verify.key.location=JWTSecret.jwk smallrye.jwt.verify.algorithm=HS256 quarkus.native.resources.includes=JWTSecret.jwk
JWTSecret.jwk文件内容
{ "kty": "oct", "k": "qwertyuiopasdfghjklzxcvbnm123456", "alg": "HS256" }
排查情况
- 已通过jwt.io用上述密钥验证令牌签名,结果正常,推测问题出在JWK文件或配置中
- 改用RS256算法(PEM公钥/私钥)验证正常,但业务要求必须使用HS256
- 业务代码在其他算法下工作正常,暂不考虑代码问题,相关代码如下:
package co.ogram.domain import org.eclipse.microprofile.jwt.JsonWebToken import javax.annotation.security.RolesAllowed import javax.enterprise.inject.Default import javax.inject.Inject import javax.ws.rs.* import javax.ws.rs.core.Context import javax.ws.rs.core.MediaType import javax.ws.rs.core.SecurityContext @Path("/secured") class TokenSecuredResource { @Inject @field:Default var jwt: JsonWebToken? = null @GET @Path("/roles-allowed") @RolesAllowed("Admin") @Produces(MediaType.TEXT_PLAIN) fun helloRolesAllowed(@Context ctx: SecurityContext): String? { return getResponseString(ctx!!) } private fun getResponseString(ctx: SecurityContext): String { val name: String name = if (ctx.userPrincipal == null) { "anonymous" } else if (ctx.userPrincipal.name != jwt!!.name) { throw InternalServerErrorException("Principal and JsonWebToken names do not match") } else { ctx.userPrincipal.name } val type = jwt!!.getClaim<Int>("type") return String.format( "hello + %s," + " isHttps: %s," + " authScheme: %s," + " type: %s," + " hasJWT: %s", name, ctx.isSecure, ctx.authenticationScheme, type, hasJwt() ) } private fun hasJwt(): Boolean { return jwt!!.claimNames != null } }
解决方案
问题出在JWK文件的k字段值——HS256的密钥在JWK中需要是Base64URL编码后的结果,而非明文直接填写。
方案1:修正JWK文件
- 将原始密钥
qwertyuiopasdfghjklzxcvbnm123456进行Base64URL编码(替换+为-、/为_,去掉末尾=),编码结果为:cXdlcnR5dWlvcGFzZGZnaGprbHp4Y3Zibm0xMjM0NTY - 修改JWTSecret.jwk文件的
k字段:
{ "kty": "oct", "k": "cXdlcnR5dWlvcGFzZGZnaGprbHp4Y3Zibm0xMjM0NTY", "alg": "HS256" }
- 重启应用后重新验证即可通过。
方案2:直接使用明文密钥(更简单)
放弃JWK格式,直接在配置中指定原始密钥:
修改application.properties:
smallrye.jwt.verify.key-format=raw smallrye.jwt.verify.key=qwertyuiopasdfghjklzxcvbnm123456 smallrye.jwt.verify.algorithm=HS256
此方式无需JWK文件,直接用原始密钥即可完成验证。
内容的提问来源于stack exchange,提问作者E. Dn

