无法在 Java 中解析 JWK
Unable to parse JWK in Java
我实现了一个休息授权服务器,它 return 使用 com.nimbusds:nimbus-jose-jwt:9.13
包为 JWK 格式的给定 keyId 提供 public-key。代码看起来像这样:
@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;
}
}
此代码 return 是一个 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 josn 主体中需要一个 kty
键,而 JWK
的默认序列化将 kty
键嵌入到 requiredParams
键中。当我尝试 jwk.toString()
时,我确实在主 json 正文中看到了 kty
键。
为什么 serialization/deserialization 本机 JWK 对象不能以直接的方式工作?在不实施自定义 jwt 结构或 serializer/deserializer 的情况下解决此问题的最佳方法是什么?
更新 1:如果我们将 return 类型从 JWK
更改为 Map<String, Object>
或 String
,此代码将起作用并在客户端处理反序列化。但是,如果包本身为我们进行(反)序列化会更好。
答案是使用 String
为面临此问题的人进行(反)序列化。你为什么问?根据RFC,JWK是JSON格式的字符串。虽然 nimbusds:nimbus-jose-jwt
定义了一个 JWK 对象,但任何 return 有效 JWK(或 JWKSet
)的 API 都可以假定它是一个字符串。
我也向这个包的开发人员提出了这个 issue,他们建议使用 String
或 Map<String, Object>
进行(反)序列化。
根据包的开发者 here, a JWK set, not individual JWK, should be exposed through the endpoint (see https://connect2id.com/products/nimbus-jose-jwt/examples/validating-jwt-access-tokens):
Publishing a JWK set (JSON array) of multiple keys, instead of just one key at the URL is intended to facilitate smooth key roll-over. With a single key being published switching to a new key can lead to errors on the client side.
Anyway, if you want to keep the one-key-per URL, I suggest you override the existing com.nimbusds.jose.jwk.source.RemoteJWKSet
, or implement your own single JWK source with com.nimbusds.jose.jwk.source.JWKSource
(possibly copying code where needed from RemoteJWKSet
).
UPDATE:刚刚发现问题中同样的序列化问题也存在于 JWKSet
我实现了一个休息授权服务器,它 return 使用 com.nimbusds:nimbus-jose-jwt:9.13
包为 JWK 格式的给定 keyId 提供 public-key。代码看起来像这样:
@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;
}
}
此代码 return 是一个 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 josn 主体中需要一个 kty
键,而 JWK
的默认序列化将 kty
键嵌入到 requiredParams
键中。当我尝试 jwk.toString()
时,我确实在主 json 正文中看到了 kty
键。
为什么 serialization/deserialization 本机 JWK 对象不能以直接的方式工作?在不实施自定义 jwt 结构或 serializer/deserializer 的情况下解决此问题的最佳方法是什么?
更新 1:如果我们将 return 类型从 JWK
更改为 Map<String, Object>
或 String
,此代码将起作用并在客户端处理反序列化。但是,如果包本身为我们进行(反)序列化会更好。
答案是使用 String
为面临此问题的人进行(反)序列化。你为什么问?根据RFC,JWK是JSON格式的字符串。虽然 nimbusds:nimbus-jose-jwt
定义了一个 JWK 对象,但任何 return 有效 JWK(或 JWKSet
)的 API 都可以假定它是一个字符串。
我也向这个包的开发人员提出了这个 issue,他们建议使用 String
或 Map<String, Object>
进行(反)序列化。
根据包的开发者 here, a JWK set, not individual JWK, should be exposed through the endpoint (see https://connect2id.com/products/nimbus-jose-jwt/examples/validating-jwt-access-tokens):
Publishing a JWK set (JSON array) of multiple keys, instead of just one key at the URL is intended to facilitate smooth key roll-over. With a single key being published switching to a new key can lead to errors on the client side.
Anyway, if you want to keep the one-key-per URL, I suggest you override the existing
com.nimbusds.jose.jwk.source.RemoteJWKSet
, or implement your own single JWK source withcom.nimbusds.jose.jwk.source.JWKSource
(possibly copying code where needed fromRemoteJWKSet
).
UPDATE:刚刚发现问题中同样的序列化问题也存在于 JWKSet