各类JWT库(java)的对比

参考和转载于:http://andaily.com/blog/?p=956

在 https://jwt.io/ 网站中收录有各类语言的JWT库实现(有关JWT详细介绍请访问 https://jwt.io/introduction/),其中JAVA语言到目前(2020-09)有6个实现库

按顺序依次是

Auth0实现 的 java-jwt
 -- maven: com.auth0 / java-jwt / 3.3.0 Brian Campbell实现的 jose4j
  -- maven: org.bitbucket.b_c / jose4j / 0.6.3 connect2id实现的 nimbus
-jose-jwt   -- maven: com.nimbusds / nimbus-jose-jwt / 5.7
Les Haziewood实现的 jjwt
  -- maven: io.jsonwebtoken / jjwt-root / 0.11.1 Inversoft实现的prime
-jwt
  -- maven: io.fusionauth / fusionauth-jwt / 3.5.0 Vertx实现的vertx
-auth-jwt.
  -- maven: io.vertx / vertx-auth-jwt / 3.5.1 

以下是各个库的使用测试

java-jwt

package myoidc.server.infrastructure;


import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.junit.Test;

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;

import static org.junit.Assert.assertNotNull;

/**
 * 2018/5/30
 * <p>
 * OAuth0  jwt
 *
 * @author Shengzhao Li
 */
public class Auth0JwtTest {


    /**
     * Test JWT
     *
     * @throws Exception Exception
     */
    @Test
    public void jwt() throws Exception {

        // RSA keyPair Generator
        final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        /*
         * 长度 至少 1024, 建议 2048
         */
        final int keySize = 2048;
        keyPairGenerator.initialize(keySize);

        final KeyPair keyPair = keyPairGenerator.genKeyPair();


        final PublicKey publicKey = keyPair.getPublic();
        final PrivateKey privateKey = keyPair.getPrivate();


        // gen id_token
        final Algorithm algorithm = Algorithm.RSA256((RSAPublicKey) publicKey, (RSAPrivateKey) privateKey);

        final String idToken = JWT.create().withJWTId("jwt-id").withAudience("audience").withSubject("subject").sign(algorithm);

        assertNotNull(idToken);
        System.out.println(idToken);


        //verify
//        final DecodedJWT decodedJWT = JWT.decode(idToken);
//        System.out.println("id_token -> header: " + decodedJWT.getHeader());
//        System.out.println("id_token -> payload: " + decodedJWT.getPayload());
//        System.out.println("id_token -> token: " + decodedJWT.getToken());
//        System.out.println("id_token -> signature: " + decodedJWT.getSignature());


        final JWTVerifier verifier = JWT.require(algorithm).build();
        final DecodedJWT verify = verifier.verify(idToken);

        assertNotNull(verify);
        System.out.println(verify);


//        final Algorithm none = Algorithm.none();

    }


}

代码地址:  https://github.com/monkeyk/MyOIDC/blob/1.1.0/myoidc-server/src/test/java/myoidc/server/infrastructure/Auth0JwtTest.java

点评:

Auth0提供的JWT库简单实用, 依赖第三方(如JAVA运行环境)提供的证书信息(keypair);有一问题是在 生成id_token与 校验(verify)id_token时都需要 公钥(public key)与密钥(private key), 个人感觉是一不足(实际上在校验时只需要public key即可)

 

jose4j

package myoidc.server.infrastructure;


import com.google.common.base.Charsets;
import com.google.common.io.CharStreams;
import org.apache.commons.lang3.RandomStringUtils;
import org.jose4j.jwe.ContentEncryptionAlgorithmIdentifiers;
import org.jose4j.jwe.JsonWebEncryption;
import org.jose4j.jwe.KeyManagementAlgorithmIdentifiers;
import org.jose4j.jwk.*;
import org.jose4j.jws.AlgorithmIdentifiers;
import org.jose4j.jws.JsonWebSignature;
import org.jose4j.jwt.JwtClaims;
import org.jose4j.jwt.consumer.JwtConsumer;
import org.jose4j.jwt.consumer.JwtConsumerBuilder;
import org.jose4j.keys.AesKey;
import org.jose4j.keys.EllipticCurves;
import org.jose4j.keys.RsaKeyUtil;
import org.junit.Test;


import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.Key;
import java.security.PrivateKey;
import java.security.PublicKey;

import static myoidc.server.Constants.*;
import static org.junit.Assert.*;


/**
 * 2016/12/25
 * <p/>
 * Testing
 * https://bitbucket.org/b_c/jose4j
 *
 * @author Shengzhao Li
 */
public class Jose4JTest {


    @Test
    public void testRsaJsonWebKey() throws Exception {

        RsaJsonWebKey jwk = RsaJwkGenerator.generateJwk(DEFAULT_KEY_SIZE);
        //sig or enc
        jwk.setUse(USE_SIG);
        jwk.setKeyId(DEFAULT_KEY_ID);
        jwk.setAlgorithm(OIDC_ALG);
//        jwk.setKeyOps();

        final String publicKeyString = jwk.toJson(JsonWebKey.OutputControlLevel.PUBLIC_ONLY);
        final String privateKeyString = jwk.toJson(JsonWebKey.OutputControlLevel.INCLUDE_PRIVATE);

        assertNotNull(publicKeyString);
        assertNotNull(privateKeyString);
//        System.out.println("PublicKey:\n" + publicKeyString);
//        System.out.println("PrivateKey:\n" + privateKeyString);


        try (InputStream is = getClass().getClassLoader().getResourceAsStream(KEYSTORE_NAME)) {
            String keyJson = CharStreams.toString(new InputStreamReader(is, Charsets.UTF_8));
            JsonWebKeySet jsonWebKeySet = new JsonWebKeySet(keyJson);
            assertNotNull(jsonWebKeySet);
            JsonWebKey jsonWebKey = jsonWebKeySet.findJsonWebKey(DEFAULT_KEY_ID, RsaKeyUtil.RSA, USE_SIG, OIDC_ALG);
            assertNotNull(jsonWebKey);
//            System.out.println(jsonWebKey);
        }


    }


    /**
     * RSA 加密与解密, 256位
     *
     * @since 1.1.0
     */
    @Test
    public void aesEncryptDecryptRSA() throws Exception {

        RsaJsonWebKey jwk = RsaJwkGenerator.generateJwk(2048);
        jwk.setKeyId(keyId());

        final String publicKeyString = jwk.toJson(JsonWebKey.OutputControlLevel.PUBLIC_ONLY);
        final String privateKeyString = jwk.toJson(JsonWebKey.OutputControlLevel.INCLUDE_PRIVATE);

        String data = "I am marico 3";
        //加密
        JsonWebEncryption jwe = new JsonWebEncryption();
        jwe.setAlgorithmHeaderValue(KeyManagementAlgorithmIdentifiers.RSA_OAEP_256);
        jwe.setEncryptionMethodHeaderParameter(ContentEncryptionAlgorithmIdentifiers.AES_256_CBC_HMAC_SHA_512);
        PublicKey publicKey = RsaJsonWebKey.Factory.newPublicJwk(publicKeyString).getPublicKey();
        jwe.setKey(publicKey);
        jwe.setPayload(data);

        String idToken = jwe.getCompactSerialization();
        assertNotNull(idToken);
        System.out.println(data + " ->>: " + idToken);


        //解密
        JsonWebEncryption jwe2 = new JsonWebEncryption();
        PrivateKey privateKey = RsaJsonWebKey.Factory.newPublicJwk(privateKeyString).getPrivateKey();
        jwe2.setKey(privateKey);
//        jwe2.setKey(jwk.getRsaPrivateKey());
        jwe2.setCompactSerialization(idToken);

        final String payload = jwe2.getPayload();
        assertNotNull(payload);
        assertEquals(payload, data);

    }


    /*
    * AES 加密与解密, 128位
    * */
    @Test
    public void aesEncryptDecrypt128() throws Exception {

        String keyText = "iue98623diDEs096";
        String data = "I am marico";
        Key key = new AesKey(keyText.getBytes());

        //加密
        JsonWebEncryption jwe = new JsonWebEncryption();
        jwe.setAlgorithmHeaderValue(KeyManagementAlgorithmIdentifiers.A128KW);
        jwe.setEncryptionMethodHeaderParameter(ContentEncryptionAlgorithmIdentifiers.AES_128_CBC_HMAC_SHA_256);
        jwe.setKey(key);
        jwe.setPayload(data);

        String idToken = jwe.getCompactSerialization();
        assertNotNull(idToken);
        System.out.println(data + " idToken: " + idToken);

        //解密
        JsonWebEncryption jwe2 = new JsonWebEncryption();
        jwe2.setKey(key);
        jwe2.setCompactSerialization(idToken);

        final String payload = jwe2.getPayload();
        assertNotNull(payload);
        assertEquals(payload, data);

    }


    /*
    * AES 加密与解密, 256位
    * */
    @Test
    public void aesEncryptDecrypt256() throws Exception {

        String keyText = "iue98623diDEs096_8u@idls(*JKse09";
        String data = "I am marico";
        Key key = new AesKey(keyText.getBytes());

        //加密
        JsonWebEncryption jwe = new JsonWebEncryption();
        jwe.setAlgorithmHeaderValue(KeyManagementAlgorithmIdentifiers.A256KW);
        jwe.setEncryptionMethodHeaderParameter(ContentEncryptionAlgorithmIdentifiers.AES_256_CBC_HMAC_SHA_512);
        jwe.setKey(key);
        jwe.setPayload(data);

        String idToken = jwe.getCompactSerialization();
        assertNotNull(idToken);
        System.out.println(data + " idToken: " + idToken);

        //解密
        JsonWebEncryption jwe2 = new JsonWebEncryption();
        jwe2.setKey(key);
        jwe2.setCompactSerialization(idToken);

        final String payload = jwe2.getPayload();
        assertNotNull(payload);
        assertEquals(payload, data);

    }


    /**
     * JWT 生成 idToken, 并进行消费(consume)
     * 算法:RSA SHA256
     *
     * @throws Exception
     */
    @Test
    public void jwtIdTokenConsumer() throws Exception {

        String keyId = keyId();

        //生成idToken
        JwtClaims claims = getJwtClaims();

        JsonWebSignature jws = new JsonWebSignature();
        jws.setPayload(claims.toJson());
        jws.setKeyIdHeaderValue(keyId);
        // Set the signature algorithm on the JWT/JWS that will integrity protect the claims
        jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256);


        RsaJsonWebKey jwk = RsaJwkGenerator.generateJwk(2048);
        jwk.setKeyId(keyId);
        //set private key
        jws.setKey(jwk.getPrivateKey());

//        jwk.toJson(JsonWebKey.OutputControlLevel.PUBLIC_ONLY);

        final String idToken = jws.getCompactSerialization();
        assertNotNull(idToken);
        System.out.println("idToken: " + idToken);


        //解析idToken, 验签
        JwtConsumer jwtConsumer = new JwtConsumerBuilder()
                .setRequireExpirationTime() // the JWT must have an expiration time
                .setMaxFutureValidityInMinutes(300) // but the  expiration time can't be too crazy
                .setAllowedClockSkewInSeconds(30) // allow some leeway in validating time based claims to account for clock skew
                .setRequireSubject() // the JWT must have a subject claim
                .setExpectedIssuer("Issuer") // whom the JWT needs to have been issued by
                .setExpectedAudience("Audience") // to whom the JWT is intended for
                //公钥
                .setVerificationKey(jwk.getKey()) // verify the signature with the public key
                .build(); // create the JwtConsumer instance

        final JwtClaims jwtClaims = jwtConsumer.processToClaims(idToken);
        assertNotNull(jwtClaims);
        System.out.println(jwtClaims);

    }

    private String keyId() {
        return RandomStringUtils.random(32, true, true);
    }

    private JwtClaims getJwtClaims() {
        JwtClaims claims = new JwtClaims();
        claims.setIssuer("Issuer");  // who creates the token and signs it
        claims.setAudience("Audience"); // to whom the token is intended to be sent
        claims.setExpirationTimeMinutesInTheFuture(10); // time when the token will expire (10 minutes from now)
        claims.setGeneratedJwtId(); // a unique identifier for the token
        claims.setIssuedAtToNow();  // when the token was issued/created (now)
        claims.setNotBeforeMinutesInThePast(2); // time before which the token is not yet valid (2 minutes ago)
        claims.setSubject("subject"); // the subject/principal is whom the token is about
        claims.setClaim("email", "mail@example.com"); // additional claims/attributes about the subject can be added
        return claims;
    }


    /**
     * JWT 生成 idToken+加密, 进行消费(consume)
     * 使用EC
     *
     * @throws Exception
     */
    @Test
    public void jwtECIdTokenConsumer() throws Exception {

//        String keyId = GuidGenerator.generate();
        EllipticCurveJsonWebKey sendJwk = EcJwkGenerator.generateJwk(EllipticCurves.P256);
        sendJwk.setKeyId(keyId());

        final String publicKeyString = sendJwk.toJson(JsonWebKey.OutputControlLevel.PUBLIC_ONLY);
        final String privateKeyString = sendJwk.toJson(JsonWebKey.OutputControlLevel.INCLUDE_PRIVATE);
        System.out.println("publicKeyString: " + publicKeyString);
        System.out.println("privateKeyString: " + privateKeyString);

        //生成 idToken
        final JwtClaims jwtClaims = getJwtClaims();
        JsonWebSignature jws = new JsonWebSignature();
        jws.setPayload(jwtClaims.toJson());
        //私钥
        jws.setKey(sendJwk.getPrivateKey());
        jws.setKeyIdHeaderValue(sendJwk.getKeyId());
        jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.ECDSA_USING_P256_CURVE_AND_SHA256);

        String innerIdToken = jws.getCompactSerialization();
        assertNotNull(innerIdToken);
        System.out.println("innerIdToken: " + innerIdToken);


        //对 idToken 进行加密
        JsonWebEncryption jwe = new JsonWebEncryption();
        jwe.setAlgorithmHeaderValue(KeyManagementAlgorithmIdentifiers.ECDH_ES_A128KW);
        String encAlg = ContentEncryptionAlgorithmIdentifiers.AES_128_CBC_HMAC_SHA_256;
        jwe.setEncryptionMethodHeaderParameter(encAlg);


        EllipticCurveJsonWebKey receiverJwk = EcJwkGenerator.generateJwk(EllipticCurves.P256);
        receiverJwk.setKeyId(keyId());

        jwe.setKey(receiverJwk.getPublicKey());
        jwe.setKeyIdHeaderValue(receiverJwk.getKeyId());

        jwe.setContentTypeHeaderValue("JWT");
        jwe.setPayload(innerIdToken);

        String idToken = jwe.getCompactSerialization();
        assertNotNull(idToken);
        System.out.println("idToken: " + idToken);


        //解析idToken, 验签
        JwtConsumer jwtConsumer = new JwtConsumerBuilder()
                .setRequireExpirationTime() // the JWT must have an expiration time
                .setRequireSubject() // the JWT must have a subject claim
                .setExpectedIssuer("Issuer") // whom the JWT needs to have been issued by
                .setExpectedAudience("Audience") // to whom the JWT is intended for
                //解密的私钥
                .setDecryptionKey(receiverJwk.getPrivateKey()) // decrypt with the receiver's private key
                //验签的公钥
                .setVerificationKey(sendJwk.getPublicKey()) // verify the signature with the sender's public key
                .build(); // create the JwtConsumer instance

        final JwtClaims claims = jwtConsumer.processToClaims(idToken);
        assertNotNull(claims);
        System.out.println(claims);


    }


}

代码地址: https://github.com/monkeyk/MyOIDC/blob/1.1.0/myoidc-server/src/test/java/myoidc/server/infrastructure/Jose4JTest.java

点评:

jose4j提供了完整的JWT实现, 可以不依赖第三方提供的证书信息(keypair, 库本身自带有RSA的实现),类定义与JWT协议规定匹配度高,易理解与上手对称加密与非对称加密都有提供实现

 

nimbus-jose-jwt

package myoidc.server.infrastructure;

import com.google.common.base.Charsets;
import com.google.common.io.CharStreams;
import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.*;
import com.nimbusds.jose.jwk.Curve;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jwt.EncryptedJWT;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import net.minidev.json.JSONObject;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.io.InputStreamReader;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Date;
import java.util.List;

import static org.junit.Assert.*;


/**
 * 2016/12/25
 * <p/>
 * Testing
 * http://connect2id.com/products/nimbus-jose-jwt
 *
 * @author Shengzhao Li
 */
public class NimbusJoseJwtTest {


    /**
     * @throws Exception e
     * @since 1.1.0
     */
    @Test
    @Ignore
    public void testJWKSet() throws Exception {

        Resource resource = new ClassPathResource("classpath*:keystore.jwks");
        // read in the file
        String s = CharStreams.toString(new InputStreamReader(resource.getInputStream(), Charsets.UTF_8));
        JWKSet jwkSet = JWKSet.parse(s);
        assertNotNull(jwkSet);
//        System.out.println(jwkSet);

        List<JWK> keys = jwkSet.getKeys();
        for (JWK key : keys) {
//            System.out.println(key);
//            System.out.println(key.getAlgorithm());
//            System.out.println(key.getKeyStore());
//            System.out.println(key.getKeyUse());
//            System.out.println(key.getKeyType());
//            System.out.println(key.getParsedX509CertChain());
            System.out.println(key.getKeyID());
            System.out.println(key.isPrivate());

//            JWK jwk = key.toPublicJWK();
//            System.out.println(jwk);
//            JSONObject jsonObject = key.toJSONObject();
//            System.out.println(jsonObject);

//            PublicJsonWebKey rsk = RsaJsonWebKey.Factory.newPublicJwk(key.toString());
//            PrivateKey privateKey = rsk.getPrivateKey();
//            PublicKey publicKey = rsk.getPublicKey();
//            System.out.println(publicKey + "\n" + privateKey);

//            RSAKey  rsaKey= new RSAKey();
//            rsaKey.
        }
    }


    /**
     * JWS
     * 使用HMAC SHA-256 进行加密 与 解密
     * 基于相同的 secret (对称算法)
     * <p/>
     * 算法     Secret长度
     * HS256   32
     * HS384   64
     * HS512   64
     *
     * @throws Exception
     */
    @Test
    public void jwsMAC() throws Exception {

        String sharedSecret = RandomStringUtils.random(64, true, true);
        JWSSigner jwsSigner = new MACSigner(sharedSecret);

        //加密
//        JWSHeader header = new JWSHeader(JWSAlgorithm.HS256);
//        JWSHeader header = new JWSHeader(JWSAlgorithm.HS384);
        JWSHeader header = new JWSHeader(JWSAlgorithm.HS512);
        final String payloadText = "I am MyOIDC";
        Payload payload = new Payload(payloadText);
        JWSObject jwsObject = new JWSObject(header, payload);

        jwsObject.sign(jwsSigner);
        //获取 idToken
        final String idToken = jwsObject.serialize();
        System.out.println(payloadText + " -> id_token: " + idToken);

        //解密
        JWSVerifier verifier = new MACVerifier(sharedSecret);
        final JWSObject parseJWS = JWSObject.parse(idToken);
        final boolean verify = parseJWS.verify(verifier);

        assertTrue(verify);
        final String decryptPayload = parseJWS.getPayload().toString();
        assertEquals(decryptPayload, payloadText);
    }

    /**
     * JWT
     * 使用HMAC SHA-256 进行加密 与 解密
     * 基于相同的 secret (对称算法)
     * <p/>
     * 算法     Secret长度
     * HS256   32
     * HS384   64
     * HS512   64
     *
     * @throws Exception
     */
    @Test
    public void jwtMAC() throws Exception {

        String sharedSecret = RandomStringUtils.random(64, true, true);
        JWSSigner jwsSigner = new MACSigner(sharedSecret);

        //生成idToken
        final String payloadText = "I am MyOIDC";
        JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
                .subject("subject")
                .issuer("https://andaily.com")
                .claim("payloadText", payloadText)
                .expirationTime(new Date(new Date().getTime() + 60 * 1000))
                .build();

//        final JWSHeader header = new JWSHeader(JWSAlgorithm.HS256);
//        final JWSHeader header = new JWSHeader(JWSAlgorithm.HS384);
        final JWSHeader header = new JWSHeader(JWSAlgorithm.HS512);
        SignedJWT signedJWT = new SignedJWT(header, claimsSet);
        signedJWT.sign(jwsSigner);

        final String idToken = signedJWT.serialize();

        //校验idToken
        final SignedJWT parseJWT = SignedJWT.parse(idToken);
        JWSVerifier jwsVerifier = new MACVerifier(sharedSecret);
        final boolean verify = parseJWT.verify(jwsVerifier);

        assertTrue(verify);
//        final Payload payload = parseJWT.getPayload();
        final JWTClaimsSet jwtClaimsSet = parseJWT.getJWTClaimsSet();
        assertEquals(jwtClaimsSet.getSubject(), "subject");

    }


    /**
     * JWS
     * 使用 RSA 算法 生成 id_token
     * 以及对其进行校验(verify)
     * 需要公私钥对
     * <p/>
     * 支持算法
     * RS256
     * RS384
     * RS512
     *
     * @throws Exception
     */
    @Test
    public void jwsRSA() throws Exception {

        // RSA keyPair Generator
        final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        /**
         * 长度 至少 1024, 建议 2048
         */
        final int keySize = 2048;
        keyPairGenerator.initialize(keySize);

        final KeyPair keyPair = keyPairGenerator.genKeyPair();
        //公钥
        final RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        //私钥
        final PrivateKey privateKey = keyPair.getPrivate();

        //keyId
        String keyId = RandomUtils.randomNumber();

        //生成id_token
        JWSSigner jwsSigner = new RSASSASigner(privateKey);

//        JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256).keyID(keyId).build();
//        JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS384).keyID(keyId).build();
        JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS512).keyID(keyId).build();

        final String payloadText = "I am MyOIDC [RSA]";
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("Issuer", "Issuer");
        jsonObject.put("Audience", "Audience");
        jsonObject.put("payloadText", payloadText);

//        Payload payload = new Payload(payloadText);
        Payload payload = new Payload(jsonObject);
        JWSObject jwsObject = new JWSObject(header, payload);

        jwsObject.sign(jwsSigner);
        final String idToken = jwsObject.serialize();
        System.out.println(payloadText + " -> id_token: " + idToken);


        //校验 id_token
        final JWSObject parseJWS = JWSObject.parse(idToken);

        JWSVerifier verifier = new RSASSAVerifier(publicKey);
        final boolean verify = parseJWS.verify(verifier);
        assertTrue(verify);

        final Payload payload1 = parseJWS.getPayload();
        assertNotNull(payload1);
        final JSONObject jsonObject1 = payload1.toJSONObject();
        assertNotNull(jsonObject1);

        assertEquals(payloadText, jsonObject1.get("payloadText"));

    }


    /**
     * JWT
     * 使用 RSA 算法 生成 id_token
     * 以及对其进行校验(verify)
     * 需要公私钥对
     * <p/>
     * 支持算法
     * RS256
     * RS384
     * RS512
     *
     * @throws Exception
     */
    @Test
    public void jwtRSA() throws Exception {

        // RSA keyPair Generator
        final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        /**
         * 长度 至少 1024, 建议 2048
         */
        final int keySize = 2048;
        keyPairGenerator.initialize(keySize);

        final KeyPair keyPair = keyPairGenerator.genKeyPair();
        //公钥
        final RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        //私钥
        final PrivateKey privateKey = keyPair.getPrivate();

        //keyId
        String keyId = RandomUtils.randomNumber();

        //生成id_token
        JWSSigner jwsSigner = new RSASSASigner(privateKey);

//        JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256).keyID(keyId).build();
//        JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS384).keyID(keyId).build();
        JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS512).keyID(keyId).build();

        final String payloadText = "I am MyOIDC [RSA]";
        JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
                .subject("subject")
                .issuer("Issuer")
                .audience("Audience")
                .claim("payloadText", payloadText)
                .expirationTime(new Date(new Date().getTime() + 60 * 1000))
                .build();

        SignedJWT signedJWT = new SignedJWT(header, claimsSet);

        signedJWT.sign(jwsSigner);
        final String idToken = signedJWT.serialize();
        System.out.println(payloadText + " -> id_token: " + idToken);


        //校验 id_token
        final SignedJWT parseJWT = SignedJWT.parse(idToken);

        JWSVerifier verifier = new RSASSAVerifier(publicKey);
        final boolean verify = parseJWT.verify(verifier);
        assertTrue(verify);

        final JWTClaimsSet jwtClaimsSet = parseJWT.getJWTClaimsSet();
        assertNotNull(jwtClaimsSet);
        assertEquals(payloadText, jwtClaimsSet.getStringClaim("payloadText"));


    }


    /**
     * JWS
     * 使用 EC 算法 生成 id_token
     * 以及对其进行校验(verify)
     * 需要公私钥对
     * <p/>
     * 支持算法
     * ES256
     * ES384
     * ES512
     *
     * @throws Exception
     */
    @Test
    public void jwsEC() throws Exception {

        //EC KeyPair
        KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance("EC");
//        keyGenerator.initialize(ECKey.Curve.P_256.toECParameterSpec());
//        keyGenerator.initialize(ECKey.Curve.P_384.toECParameterSpec());
        keyGenerator.initialize(Curve.P_521.toECParameterSpec());
        KeyPair keyPair = keyGenerator.generateKeyPair();

        ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic();
        ECPrivateKey privateKey = (ECPrivateKey) keyPair.getPrivate();

        //keyId
        String keyId = RandomUtils.randomNumber();

        //生成id_token
        JWSSigner signer = new ECDSASigner(privateKey);

//        JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.ES256).keyID(keyId).build();
//        JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.ES384).keyID(keyId).build();
        JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.ES512).keyID(keyId).build();

        final String payloadText = "I am MyOIDC [ECDSA]";
        Payload payload = new Payload(payloadText);
        JWSObject jwsObject = new JWSObject(header, payload);

        jwsObject.sign(signer);
        final String idToken = jwsObject.serialize();
        System.out.println(payloadText + " -> id_token: " + idToken);


        //校验 id_token
        final JWSObject parseJWS = JWSObject.parse(idToken);

        JWSVerifier verifier = new ECDSAVerifier(publicKey);
        final boolean verify = parseJWS.verify(verifier);

        assertTrue(verify);
        final String s = parseJWS.getPayload().toString();
        assertEquals(s, payloadText);

    }


    /**
     * JWT
     * 使用 EC 算法 生成 id_token
     * 以及对其进行校验(verify)
     * 需要公私钥对
     * <p/>
     * 支持算法
     * ES256
     * ES384
     * ES512
     *
     * @throws Exception
     */
    @Test
    public void jwtEC() throws Exception {

        //EC KeyPair
        KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance("EC");
//        keyGenerator.initialize(ECKey.Curve.P_256.toECParameterSpec());
//        keyGenerator.initialize(ECKey.Curve.P_384.toECParameterSpec());
        keyGenerator.initialize(Curve.P_521.toECParameterSpec());
        KeyPair keyPair = keyGenerator.generateKeyPair();

        ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic();
        ECPrivateKey privateKey = (ECPrivateKey) keyPair.getPrivate();

        //keyId
        String keyId = RandomUtils.randomNumber();

        //生成id_token
        JWSSigner signer = new ECDSASigner(privateKey);

//        JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.ES256).keyID(keyId).build();
//        JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.ES384).keyID(keyId).build();
        JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.ES512).keyID(keyId).build();

        final String payloadText = "I am MyOIDC [ECDSA]";
        JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
                .subject("subject")
                .issuer("Issuer")
                .audience("Audience")
                .claim("payloadText", payloadText)
                .expirationTime(new Date(new Date().getTime() + 60 * 1000))
                .build();
        SignedJWT signedJWT = new SignedJWT(header, claimsSet);

        signedJWT.sign(signer);
        final String idToken = signedJWT.serialize();
        System.out.println(payloadText + " -> id_token: " + idToken);


        //校验 id_token
        final SignedJWT parseJWS = SignedJWT.parse(idToken);

        JWSVerifier verifier = new ECDSAVerifier(publicKey);
        final boolean verify = parseJWS.verify(verifier);

        assertTrue(verify);
        final JWTClaimsSet jwtClaimsSet = parseJWS.getJWTClaimsSet();
        assertEquals(jwtClaimsSet.getClaim("payloadText"), payloadText);

    }


    /**
     * 使用RSA 算法进行加密数据
     * 与解密数据
     * <p/>
     * 128
     * RSA_OAEP   - A128GCM
     * 256
     * RSA_OAEP_256 - A256GCM
     *
     * @throws Exception
     */
    @Test
    public void jwtRSAEncryption() throws Exception {

        // RSA keyPair Generator
        final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        /**
         * 长度 至少 1024, 建议 2048
         */
        final int keySize = 2048;
        keyPairGenerator.initialize(keySize);

        final KeyPair keyPair = keyPairGenerator.genKeyPair();
        //公钥
        final RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        //私钥
        final PrivateKey privateKey = keyPair.getPrivate();


        //加密, 生成idToken
        //加密的数据放在 JWTClaimsSet 中
        JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
                .issuer("https://myoidc.cc")
                .subject("Lims")
                .audience("https://one-app.com")
                .notBeforeTime(new Date())
                .issueTime(new Date())
                .expirationTime(new Date(new Date().getTime() + 1000 * 60 * 10))
                .jwtID(RandomStringUtils.random(16, true, true))
                .build();

//        JWEHeader header = new JWEHeader(JWEAlgorithm.RSA_OAEP, EncryptionMethod.A128GCM);
        JWEHeader header = new JWEHeader(JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A256GCM);
        EncryptedJWT jwt = new EncryptedJWT(header, claimsSet);

        RSAEncrypter encrypter = new RSAEncrypter(publicKey);
        jwt.encrypt(encrypter);

        final String idToken = jwt.serialize();
        assertNotNull(idToken);

        //解密
        final EncryptedJWT parseJWT = EncryptedJWT.parse(idToken);
        RSADecrypter decrypter = new RSADecrypter(privateKey);
        parseJWT.decrypt(decrypter);

        final JWTClaimsSet jwtClaimsSet = parseJWT.getJWTClaimsSet();
        assertNotNull(jwtClaimsSet);
        assertNotNull(jwtClaimsSet.getAudience());

    }


    /**
     * AES 加密/解密
     * JWE
     *
     * @throws Exception
     */
    @Test
    public void jweAES() throws Exception {

        final KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
        //位数
//        keyGenerator.init(128);
        keyGenerator.init(256);
        final SecretKey secretKey = keyGenerator.generateKey();

        //加密
//        JWEHeader jweHeader = new JWEHeader(JWEAlgorithm.DIR, EncryptionMethod.A128GCM);
        JWEHeader jweHeader = new JWEHeader(JWEAlgorithm.DIR, EncryptionMethod.A256GCM);
        Payload payload = new Payload("I am MyOIDC");

        JWEObject jweObject = new JWEObject(jweHeader, payload);
        jweObject.encrypt(new DirectEncrypter(secretKey));

        final String idToken = jweObject.serialize();
        assertNotNull(idToken);

        //解密
        final JWEObject parseJWE = JWEObject.parse(idToken);
        parseJWE.decrypt(new DirectDecrypter(secretKey));

        final Payload payload1 = parseJWE.getPayload();
        assertNotNull(payload1);

    }


}

代码地址: https://github.com/monkeyk/MyOIDC/blob/1.1.0/myoidc-server/src/test/java/myoidc/server/infrastructure/NimbusJoseJwtTest.java

点评:

nimbus-jose-jwt库类定义清晰,简单易用,易理解 , 依赖第三方提供的证书信息(keypair), 对称算法 与非对称算法皆有实现.

 

jjwt

package myoidc.server.infrastructure;


import io.jsonwebtoken.*;
import org.apache.commons.lang3.time.DateUtils;
import org.junit.Test;

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Date;

import static org.junit.Assert.assertNotNull;

/**
 * 2018/5/30
 * <p>
 * <p>
 * Test JJWT  lib
 *
 * @author Shengzhao Li
 */
public class JJwtTest {


    @Test
    public void idToken() throws Exception {

        // RSA keyPair Generator
        final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        /*
         * 长度 至少 1024, 建议 2048
         */
        final int keySize = 2048;
        keyPairGenerator.initialize(keySize);

        final KeyPair keyPair = keyPairGenerator.genKeyPair();

        final PrivateKey privateKey = keyPair.getPrivate();

        // gen id_token
        final Date exp = DateUtils.addMinutes(new Date(), 5);
        final JwtBuilder jwtBuilder = Jwts.builder().setId("jti").setSubject("sub").setExpiration(exp).signWith(SignatureAlgorithm.RS512, privateKey);
        final String idToken = jwtBuilder.compact();


        assertNotNull(idToken);
        System.out.println(idToken);


        // verify

        final PublicKey publicKey = keyPair.getPublic();
//        final Jwt jwt = Jwts.parser().parse(idToken);
        final JwtParser parser = Jwts.parser();
        final Jwt jwt = parser.setSigningKey(publicKey).parse(idToken);

        assertNotNull(jwt);
        System.out.println(jwt.getHeader());
        System.out.println(jwt.getBody());


    }


}

代码地址: https://github.com/monkeyk/MyOIDC/blob/1.1.0/myoidc-server/src/test/java/myoidc/server/infrastructure/JJwtTest.java

点评:

jjwt小巧够用, 但对JWT的一些细节包装不够, 比如 Claims (只提供获取header,body)

 

prime-jwt

package myoidc.server.infrastructure;


import org.junit.Test;
import org.primeframework.jwt.JWTUtils;
import org.primeframework.jwt.domain.JWT;
import org.primeframework.jwt.domain.RSAKeyPair;
import org.primeframework.jwt.hmac.HMACSigner;
import org.primeframework.jwt.hmac.HMACVerifier;
import org.primeframework.jwt.rsa.RSASigner;
import org.primeframework.jwt.rsa.RSAVerifier;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

/**
 * 2018/6/2
 * <p>
 * Testing
 * https://github.com/inversoft/prime-jwt
 *
 * @author Shengzhao Li
 */
public class PrimeJwtTest {


    /**
     * Test RSA   非对称算法
     *
     * @throws Exception Exception
     */
    @Test
    public void jwtRSA() throws Exception {


        // keypair
        final RSAKeyPair rsaKeyPair = JWTUtils.generate2048RSAKeyPair();

        System.out.println("PublicKey: " + rsaKeyPair.publicKey);
        System.out.println("PrivateKey: " + rsaKeyPair.privateKey);


//        final RSAPublicKey publicKey = RSAUtils.getPublicKeyFromPEM(rsaKeyPair.publicKey);
//        final RSAPrivateKey privateKey = RSAUtils.getPrivateKeyFromPEM(rsaKeyPair.privateKey);

        // generate
        final RSASigner rsaSigner = RSASigner.newSHA256Signer(rsaKeyPair.privateKey);
        final JWT jwt = new JWT().setSubject("subject").setAudience("audi").setUniqueId("uid-id");
        final String idToken = JWT.getEncoder().encode(jwt, rsaSigner);

        assertNotNull(idToken);
        System.out.println(idToken);

        //verify
        final RSAVerifier rsaVerifier = RSAVerifier.newVerifier(rsaKeyPair.publicKey);
        final JWT decode = JWT.getDecoder().decode(idToken, rsaVerifier);

        assertNotNull(decode);
        assertEquals(decode.audience, "audi");

    }

    /**
     * Test HMAC, 对称算法
     *
     * @throws Exception Exception
     */
    @Test
    public void jwtHMAC() throws Exception {


        // secret
        final String secret = JWTUtils.generateSHA256HMACSecret();

        System.out.println("secret: " + secret);


        // generate

        final JWT jwt = new JWT().setSubject("subject").setAudience("audi").setUniqueId("uid-id");
        final HMACSigner hmacSigner = HMACSigner.newSHA256Signer(secret);
        final String idToken = JWT.getEncoder().encode(jwt, hmacSigner);

        assertNotNull(idToken);
        System.out.println(idToken);

        //verify
        final HMACVerifier hmacVerifier = HMACVerifier.newVerifier(secret);
        final JWT decode = JWT.getDecoder().decode(idToken, hmacVerifier);

        assertNotNull(decode);
        assertEquals(decode.audience, "audi");

    }


}

完整测试链接: https://github.com/monkeyk/MyOIDC/blob/1.1.0/myoidc-server/src/test/java/myoidc/server/infrastructure/PrimeJwtTest.java

点评:

prime jwt库怎么说呢, 有些地方不符合JAVA语言规范, 支持对称算法(HMAC) 与非对称算法(RSA), 也算容易理解

 

vertx-auth-jwt

package myoidc.server.infrastructure;


import io.vertx.core.json.JsonObject;
import io.vertx.ext.jwt.JWK;
import io.vertx.ext.jwt.JWT;
import io.vertx.ext.jwt.JWTOptions;
import org.junit.Test;

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Base64;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

/**
 * 2018/6/2
 * <p>
 * Test
 * https://github.com/vert-x3/vertx-auth
 *
 * @author Shengzhao Li
 */
public class VertxAuthJwtTest {


    /**
     * Generate/ Verify
     *
     * @throws Exception Exception
     */
    @Test
    public void jwt() throws Exception {


        // RSA keyPair Generator
        final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        /*
         * 长度 至少 1024, 建议 2048
         */
        final int keySize = 2048;
        keyPairGenerator.initialize(keySize);

        final KeyPair keyPair = keyPairGenerator.genKeyPair();
        //公钥
        final PublicKey publicKey = keyPair.getPublic();
        //私钥
        final PrivateKey privateKey = keyPair.getPrivate();


        final String pemPub = Base64.getEncoder().encodeToString(publicKey.getEncoded());
        final String pemSec = Base64.getEncoder().encodeToString(privateKey.getEncoded());


        //generate
        final String algorithm = "RS256";
        JWK jwk = new JWK(algorithm, pemPub, pemSec);
        final JWT jwt = new JWT().addJWK(jwk);


        JsonObject payload = new JsonObject();
        payload.put("appid", "appid");

        JWTOptions options = new JWTOptions();
        options.setAlgorithm(algorithm);
        options.setSubject("subject");

        String idToken = jwt.sign(payload, options);

        assertNotNull(idToken);
        System.out.println(idToken);


        //verify
        JWK jwk2 = new JWK(algorithm, pemPub, pemSec);
        final JWT jwtVerify = new JWT().addJWK(jwk2);

        final JsonObject decode = jwtVerify.decode(idToken);
        assertNotNull(decode);
        assertEquals(decode.getString("appid"), "appid");

    }


}

代码地址: https://github.com/monkeyk/MyOIDC/blob/1.1.0/myoidc-server/src/test/java/myoidc/server/infrastructure/VertxAuthJwtTest.java

 点评:

Vertx Auth Jwt 库算是最不容易理解的一个库了.花了不少时间才弄通这一示例. 不容易上手. 并且生成与校验id_token 时都需要公钥与私钥,不足.

 

———————————————————

以下是在使用中的一些总结或注意点

1. 几乎所有库都要求JAVA版本1.7或更高版本, 1.6或以下的版本需要二次开发(或不支持)

2.从易用性, 扩展性, 完整性等来看, 使用首先推荐 jose4j, 其次是 Nimbus-jose-jwt.

3. JWT是实现OIDC的基石,掌握其使用对实现OIDC有很大帮助(同时对JAVA证书使用, PKI体系的掌握也有要求)

posted @ 2020-09-15 21:33  Brian_Huang  阅读(8658)  评论(1编辑  收藏  举报