- changed status to open
Different handling of Date claims in Signed and Plain JWT Tokens
Issue #310
wontfix
I find some different handlings of Claimsets
Here is my testcase
– added this libraries (gradle)
testImplementation 'org.mock-server:mockserver-junit-rule:5.10.0'
testImplementation 'com.nimbusds:oauth2-oidc-sdk:8.19'
– create a testCase
import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.RSASSASigner;
import com.nimbusds.jose.jwk.KeyUse;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.gen.RSAKeyGenerator;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.jwk.source.RemoteJWKSet;
import com.nimbusds.jose.proc.*;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.PlainJWT;
import com.nimbusds.jwt.SignedJWT;
import com.nimbusds.jwt.proc.ConfigurableJWTProcessor;
import com.nimbusds.jwt.proc.DefaultJWTClaimsVerifier;
import com.nimbusds.jwt.proc.DefaultJWTProcessor;
import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
import net.minidev.json.JSONObject;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.mockserver.client.MockServerClient;
import org.mockserver.junit.MockServerRule;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.ParseException;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.UUID;
import static org.mockserver.model.HttpRequest.request;
import static org.mockserver.model.HttpResponse.response;
public class Main {
@Rule
public MockServerRule mockServerRule = new MockServerRule(this);
private MockServerClient mockServerClient;
RSAKey key;
Integer port;
ClaimsSet plainClaimSet;
JWTClaimsSet signedClaimsSet;
Date exp;
@Before
public void init() throws JOSEException, BadJOSEException, ParseException, MalformedURLException {
key = new RSAKeyGenerator(2048)
.keyUse(KeyUse.SIGNATURE)
.keyID(UUID.randomUUID().toString())
.generate();
StringBuilder sb = new StringBuilder();
sb.append("{\n");
sb.append("\"keys\": [\n");
sb.append(key.toJSONObject().toJSONString());
sb.append("]\n");
sb.append("}");
String keyResponse = sb.toString();
port = mockServerRule.getPort();
mockServerClient = mockServerRule.getClient();
mockServerClient.when(
request().withPath("/keys")
).respond(
response()
.withBody(keyResponse)
);
JWSSigner signer = new RSASSASigner(key);
exp = new Date(new Date().getTime() + 60 * 60 * 1000);
JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
.subject("test")
.issuer("http://127.0.0.1:" + port)
.audience("test")
.claim("name", "Test User")
.claim("oid", "4711")
.issueTime(new Date())
.expirationTime(exp)
.build();
PlainJWT plainJWT = new PlainJWT(claimsSet);
SignedJWT signedJWT = new SignedJWT(
new JWSHeader.Builder(JWSAlgorithm.RS256)
.keyID(key.getKeyID())
.type(JOSEObjectType.JWT)
.build(),
claimsSet);
signedJWT.sign(signer);
String plainJWTText = plainJWT.serialize();
String signedJWTText = signedJWT.serialize();
PlainObject plainJWTObject = PlainObject.parse(plainJWTText);
JSONObject plainJSONObject = plainJWTObject.getPayload().toJSONObject();
plainClaimSet = new ClaimsSet(plainJSONObject);
ConfigurableJWTProcessor<SecurityContext> jwtProcessor =
new DefaultJWTProcessor<>();
jwtProcessor.setJWSTypeVerifier(
new DefaultJOSEObjectTypeVerifier<>(new JOSEObjectType("jwt")));
JWKSource<SecurityContext> keySource =
new RemoteJWKSet<>(new URL("http://127.0.0.1:" + port + "/keys"));
JWSAlgorithm expectedJWSAlg = JWSAlgorithm.RS256;
JWSKeySelector<SecurityContext> keySelector =
new JWSVerificationKeySelector<>(expectedJWSAlg, keySource);
jwtProcessor.setJWSKeySelector(keySelector);
jwtProcessor.setJWTClaimsSetVerifier(new DefaultJWTClaimsVerifier(
new JWTClaimsSet.Builder().issuer("http://127.0.0.1:" + port).build(),
new HashSet<>(Arrays.asList("sub", "iat", "exp"))));
SecurityContext ctx = null; // optional context parameter, not required here
signedClaimsSet = jwtProcessor.process(signedJWTText, ctx);
}
@Test
public void returnsNull() {
//this is ok
Assert.assertNull(plainClaimSet.getClaim("exp", Date.class));
}
@Test
public void longValueIsCutted(){
Assert.assertEquals(plainClaimSet.getClaim("exp", Long.class), new Long(exp.getTime()));
}
@Test
public void dateIsModified(){
Assert.assertEquals(plainClaimSet.getDateClaim("exp").getTime(), exp.getTime());
}
@Test
public void correct(){
System.out.println(signedClaimsSet.getClaim("exp").getClass());
Assert.assertNotNull(signedClaimsSet.getClaim("exp"));
}
@Test
public void correct2() throws ParseException {
System.out.println(signedClaimsSet.getDateClaim("exp").getClass());
Assert.assertNotNull(signedClaimsSet.getDateClaim("exp"));
}
@Test
public void plainAsLongIsOk() throws ParseException {
//plain is possible as long
Assert.assertNotNull(plainClaimSet.getClaim("exp", Long.class));
System.out.println(plainClaimSet.getClaim("exp", Long.class));
Assert.assertNotNull(signedClaimsSet.getLongClaim("exp"));
}
}
Comments (3)
-
-
- changed status to wontfix
It is true the ClaimsSet class from this SDK and the JWTClaimsSet class the JOSE+JWT lib handle java.util.Data get/set/internal field differently. I suppose because they were designed in separate projects. The JWTClaimsSet class automatically detects Date whereas the ClaimsSet class not.
My suggestion is to use the Date specific getters or setters and just be aware of this.
-
Commit 853271a6 adds ClaimsSet test to demonstrate
java.util.Date
handling. - Log in to comment