Different handling of Date claims in Signed and Plain JWT Tokens

Issue #310 wontfix
Robert Kupferschmied created an issue

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)

  1. Yavor Vasilev

    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.

  2. Log in to comment