jjwt
jjwt copied to clipboard
Support a way to use JWK keys
Would be really useful to support Key suppliers based on a JWS endpoint for the validation of a JWS, or any way to use JWK to verify a JWS (JWKParser?).
+1 on this. I recently created a solution that I'd like to integrate into JJWT. I think where we should draw the line is to handle retrieval of jwk given an endpoint. Since it is guaranteed by the spec to be open and will always be a simple GET
, I also think we can do this without the need to involve another external library. Here's the solution I created for a "quick and dirty" example. I will formalize it with tests to be incorporated into the jjwt.
@SuppressWarnings("unchecked")
private Map<String, String> getKeyById(Map<String, Object> jwks, String kid) {
List<Map<String, String>> keys = (List<Map<String, String>>)jwks.get("keys");
Map<String, String> ret = null;
for (int i = 0; i < keys.size(); i++) {
if (keys.get(i).get("kid").equals(kid)) {
return keys.get(i);
}
}
return ret;
}
private String simpleGet(String urlStr) {
HttpURLConnection connection = null;
try {
URL url = new URL(urlStr);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
BufferedReader rd = new BufferedReader(new InputStreamReader(connection.getInputStream()));
StringBuilder response = new StringBuilder();
String line;
while ((line = rd.readLine()) != null) {
response.append(line);
response.append('\r');
}
rd.close();
return response.toString();
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
if (connection != null) {
connection.disconnect();
}
}
}
With these two methods, it's easy to parse a JWT like so (NOTE: This example use jackson mapper for json, which is already incorporated into JJWT):
...
TypeReference<Map<String, Object>> typeRef = new TypeReference<Map<String, Object>>() {};
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> jwks = mapper.readValue(simpleGet(jwksUri), typeRef);
SigningKeyResolver resolver = new SigningKeyResolverAdapter() {
public Key resolveSigningKey(JwsHeader jwsHeader, Claims claims) {
try {
Map<String, String> jwk = getKeyById(jwks, jwsHeader.getKeyId());
BigInteger modulus = new BigInteger(1, Base64.getUrlDecoder().decode(jwk.get("n")));
BigInteger exponent = new BigInteger(1, Base64.getUrlDecoder().decode(jwk.get("e")));
return KeyFactory.getInstance("RSA").generatePublic(new RSAPublicKeySpec(modulus, exponent));
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
log.error("Failed to resolve key: {}", e.getMessage(), e);
return null;
}
}
};
try {
Jws<Claims> jwsClaims = Jwts.parser()
.setSigningKeyResolver(resolver)
.parseClaimsJws(jwt);
return jwsClaims.getBody();
} catch (Exception e) {
log.error("Couldn't parse jwt: {}", e.getMessage(), e);
return null;
}
...
TIL that JWK support is already in progress as it's a requirement for JWE support. It's not ready yet, but you can see progress here: https://github.com/jwtk/jjwt/compare/jwe#diff-9bcd9bdeb445b83fc3620c7f1ca1e87fR1
+1 for this being part of this library. In the mean time while we wait. the auth0/jwks-rsa-java project plays well with a SigningKeyResolver
https://github.com/auth0/jwks-rsa-java
Something like
import com.auth0.jwk.JwkProvider;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.SigningKeyResolver;
import java.security.Key;
public class JwkKeyResolver implements SigningKeyResolver {
private JwkProvider keyStore;
public JwkKeyResolver(JwkProvider keyStore) {
this.keyStore = keyStore;
}
@Override
public Key resolveSigningKey(JwsHeader jwsHeader, Claims claims) {
String keyId = jwsHeader.getKeyId();
Key pub = keyStore.get(keyId);
return pub;
}
}
I think it would be nice to have this in Version 1.0
If I can contribute in any way. Hit me up currently I have to use 2 Libaries and I want to only use this one
Any progress on this feature ?
Any updates?
As well as parsing externally generated JWKs it would also be useful to directly support formatting keys generated by the library as JWKs. The API provides essentially a black box for generating keys from a SignatureAlgorithm and it would be nice to be able to generate the corresponding JWK needed to verify a JWT equally easily. Itsnot hard to do by hand but not without engaging with details the library normally hides from you.
Found this library for dealing with JWKs. Hope it helps someone else in the future:
<!-- https://mvnrepository.com/artifact/com.nimbusds/nimbus-jose-jwt -->
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>8.19</version>
</dependency>
https://connect2id.com/products/nimbus-jose-jwt
@hyperxpro this will be in the JJWT 1.0 release.
any update on 1.0 release date with JWK support?
Any updates?
@ReDestroyDeR in the short term you can create a SigningKeyResolver
that looks something like:
https://github.com/okta/okta-jwt-verifier-java/blob/master/impl/src/main/java/com/okta/jwt/impl/jjwt/RemoteJwkSigningKeyResolver.java
It's unfortunate that the Okta implementation caches the keys indefinitely though. Ideally it should be cached for a certain amount of time, configurable or based on the HTTP Cache-Control response headers.
@ddevrien that impl could absolutely be improved, by the keys are not cached indefinitely (under normal usage), they will get updated when a new key-id is used, that is not a perfect solution.
Obligatory cache quote:
There are only two hard things in Computer Science: cache invalidation and naming things. -- Phil Karlton
A generic full-featured implementation should probably:
- Cache keys for length based on Cache-Control headers
- Have a configurable fallback cache length for servers without Cache-Control headers
- Replace cached keys when new key-ids are found (remote keys could be manually rotated, which would invalidate the existing local cache)
- Have a minimum (configurable?) duration between HTTP requests (to prevent potential thread starvation)
- While new keys are being requested, old keys should continue to validate tokens (remote key rotation where old and new keys overlap)
- potentially support functionality to programmatically clear cache (if a key leaks, you may want to force cache invalidation).
Full JWK support has been merged with #279 and will be released in the upcoming 0.12.0
release!
#178 was closed (not merged) in 2016; maybe you meant #279?
Oh dang, you're right, thank you!