无法解析Java中的JWK



我实现了一个rest授权服务器,它使用com.nimbusds:nimbus-jose-jwt:9.13包以JWK格式返回给定keyId的公钥。代码看起来像这样:

@RequestMapping(value = "/oauth2", produces = APPLICATION_JSON_VALUE)
public interface Rest {
...
@GetMapping("/public-key/{keyId}")
@Operation(summary = "Return the public key corresponding to the key id")
JWK getPublicKey(@PathVariable String keyId);
}
public class RestController implements Rest {
.....
public JWK getPublicKey(String keyId) {
byte[] publicKeyBytes = ....
RSAPublicKey publicKey = (RSAPublicKey) keyFactory.generatePublic(new X509EncodedKeySpec(publicKeyBytes));
JWK jwk = new RSAKey.Builder(publicKey)
.keyID(keyId)
.algorithm(new Algorithm(publicKey.getAlgorithm()))
.keyUse(KeyUse.SIGNATURE)
.build();
return jwk;
}
}

这段代码以以下格式返回一个JWK键:

{
"keyStore": null,
"private": false,
"publicExponent": {},
"modulus": {},
"firstPrimeFactor": null,
"secondPrimeFactor": null,
"firstFactorCRTExponent": null,
"secondFactorCRTExponent": null,
"firstCRTCoefficient": null,
"otherPrimes": [],
"requiredParams": {
"e": "some-valid-exponent",
"kty": "RSA",
"n": "some-valid-modulus"
},
"privateExponent": null,
"x509CertChain": null,
"algorithm": {
"name": "RSA",
"requirement": null
},
"keyOperations": null,
"keyID": "some-valid-key-id",
"x509CertURL": null,
"x509CertThumbprint": null,
"x509CertSHA256Thumbprint": null,
"parsedX509CertChain": null,
"keyUse": {
"value": "sig"
},
"keyType": {
"value": "RSA",
"requirement": "REQUIRED"
}
}

在客户端(java),我尝试用以下代码解析jwk:

public JWK getPublicKey(String keyId) {
String json = restTemplate.getForObject(publicUrl + "/oauth2/public-key/" + keyId, String.class);
try {
return JWK.parse(json);
} catch (ParseException e) {
log.error("Unable to parse JWK", e);
return null;
}
}

但是,由于parse抛出异常(Missing parameter "kty"),客户端无法解析密钥。我看到JWK.parse需要在JWT主体中使用kty键,而JWK的默认序列化在requiredParams键中嵌入kty键。当我尝试jwk.toString()时,我确实在json主体中看到kty键。

为什么本机JWK对象的序列化/反序列化不能以直接的方式工作?在不实现自定义jwt结构或序列化/反序列化器的情况下,修复这个问题的最佳方法是什么?

更新1如果我们将返回类型从JWK更改为Map<String, Object>String,并在客户端处理反序列化,则此代码将正常工作。但是,如果包本身为我们执行(反)序列化会更好。

解决方法是使用String进行(反)序列化。你会问为什么?根据RFC, JWK是JSON格式的字符串。虽然nimbusds:nimbus-jose-jwt定义了一个JWK对象,但任何返回有效JWK(或JWKSet)的api都可以假设它是一个字符串。

我也向这个包的开发人员提出了这个问题,他们建议使用StringMap<String, Object>进行(反)序列化。

根据这里包的开发人员的说法,应该通过端点公开一个JWK集,而不是单个JWK(参见https://connect2id.com/products/nimbus-jose-jwt/examples/validating-jwt-access-tokens):

)。

在URL上发布多个键的JWK集(JSON数组),而不是只有一个键,目的是为了方便顺利的键切换。在发布单个密钥时,切换到新密钥可能会导致客户端出现错误。

无论如何,如果您想保持每个URL一个键,我建议您覆盖现有的com.nimbusds.jose.jwk.source.RemoteJWKSet,或者使用com.nimbusds.jose.jwk.source.JWKSource实现您自己的单个JWK源代码(可能需要从RemoteJWKSet复制代码)。


更新:刚刚发现问题中同样的序列化问题也存在于JWKSet😔

最新更新