各类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(); } }
点评:
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); } }
点评:
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); } }
点评:
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()); } }
点评:
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"); } }
点评:
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"); } }
点评:
Vertx Auth Jwt 库算是最不容易理解的一个库了.花了不少时间才弄通这一示例. 不容易上手. 并且生成与校验id_token 时都需要公钥与私钥,不足.
———————————————————
以下是在使用中的一些总结或注意点
1. 几乎所有库都要求JAVA版本1.7或更高版本, 1.6或以下的版本需要二次开发(或不支持)
2.从易用性, 扩展性, 完整性等来看, 使用首先推荐 jose4j, 其次是 Nimbus-jose-jwt.
3. JWT是实现OIDC的基石,掌握其使用对实现OIDC有很大帮助(同时对JAVA证书使用, PKI体系的掌握也有要求)