JWT框架简单测评,哪款是你的菜

 

温故而知新,可以为师矣。

 

JWT的实现框架

从上一篇 JWT就是这么简单 知道JWT是一种标准,而不是具体的实现,那么在JAVA中实现了JWT的框架多不胜数(公司内部自己写的JWT框架)。

官方推荐是使用官方的Auth0,但是Auth0中功能远远满足不了各种需求。所以各路大神都献出自己写的JWT框架,目前得到官方认可的框架一共是6个 auth0jose4jnimbus-josejjwtfusionauthvertx

 

 

上手体验

上手体验前,朕水先写生成一个密钥、读取密钥的方法。 生成密钥代码:

public class CreatRsaKey {
   //密钥长度 于原文长度对应 以及越长速度越慢 必须大于 512
   private final static int KEY_SIZE = 2048;

   public static void main(String[] args) throws Exception {
       //一对密钥算法生成
       KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
       SecureRandom secureRandom = new SecureRandom();
       //设置随机种子
       secureRandom.setSeed(32);
       keyPairGen.initialize(KEY_SIZE,secureRandom);
       KeyPair keyPair = keyPairGen.generateKeyPair();
       // 得到私钥
       RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
       // 得到公钥
       RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
       // 得到密钥字符串
       byte[] publicKeyBytes = Base64.getEncoder().encode(publicKey.getEncoded());
       byte[] privateKeyBytes = Base64.getEncoder().encode(privateKey.getEncoded());
//保存到文件中
       Resource publicKeyResource = new FileSystemResource("src/main/resources/rsa/publickey.rsa");
       FileOutputStream publicKeyOutputStream = new FileOutputStream(publicKeyResource.getFile());
       publicKeyOutputStream.write(publicKeyBytes);
       publicKeyOutputStream.close();
//保存到文件中
       Resource privateKeyResource = new FileUrlResource("src/main/resources/rsa/privatekey.rsa");
       FileOutputStream privateKeyOutputStream = new FileOutputStream(privateKeyResource.getFile());
       privateKeyOutputStream.write(privateKeyBytes);
       privateKeyOutputStream.close();

  }
}

读取密钥代码:


public class PairKey {
   //公钥路径 src/main/resources/ec/publickey.ec
   private static final String PUBLIC_URI = "src/main/resources/rsa/publickey.rsa";
   //私钥路径 src/main/resources/ec/privatekey.ec
   private static final String PRIVATE_URI = "src/main/resources/rsa/privatekey.rsa";
   //加密类型 可以换为EC
   private static final String ALGORITHM_TYPE = "RSA";
   //存放RSA密钥对
   public static KeyPair keyPair; //注意这里嗷,这里是非对称加密的容器

   static {
       try{
           //初始化数据
           FileInputStream publicInputStream =
                   new FileInputStream(PUBLIC_URI);
           //读取公钥
           X509EncodedKeySpec bobPubKeySpec = new X509EncodedKeySpec(
                   new BASE64Decoder().decodeBuffer(publicInputStream));
           publicInputStream.close();
           //读取私钥
           FileInputStream privateInputStream =
                   new FileInputStream(PRIVATE_URI);
           PKCS8EncodedKeySpec bobPriKeySpec = new PKCS8EncodedKeySpec(
                   new BASE64Decoder().decodeBuffer(privateInputStream));
           privateInputStream.close();
           // 密钥工厂
           KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM_TYPE);
           // 取公钥对象
           PublicKey publicKey = keyFactory.generatePublic(bobPubKeySpec);
           // 取私钥对象
           PrivateKey privateKey = keyFactory.generatePrivate(bobPriKeySpec);
           // 放入容器
           keyPair = new KeyPair(publicKey,privateKey);
      } catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e) {
           e.printStackTrace();
      }
  }
}
auth0

auth0是最为简单的根据密钥(或HMAC)生成Algorithm对象,直接使用Algorithm对象加密解密。 个人认为单例生成Algorithm对象再适合不过了。

public static void main(String[] args) throws Exception {
       //RSA
       Algorithm algorithm = Algorithm
              .RSA256((RSAPublicKey) PairKey.keyPair.getPublic(), (RSAPrivateKey) PairKey.keyPair.getPrivate());
       //Algorithm algorithm = Algorithm.HMAC256("zhenshuizhenshui");
       //EC
       //Algorithm algorithm = Algorithm
       // .ECDSA256((ECPublicKey) keyPair.getPublic(), (ECPrivateKey) keyPair.getPrivate());
       //生产token
       String token = JWT.create()
               //签发人
              .withIssuer("zhenshui")
               //自定义信息
              .withClaim("username","zhenshuizhenshui")
              .withClaim("isAuth","0")
              .sign(algorithm);
       System.out.println(token);
       System.out.println("--------校验token---------------");
       JWTVerifier verifier = JWT.require(algorithm)
              .withIssuer("zhenshui")
              .build(); //Reusable verifier instance
       DecodedJWT jwt = verifier.verify(token);
       Map<String, Claim> claims = jwt.getClaims();
       //获取签发的字段
       Claim username = claims.get("username");
       System.out.println(username.asString());
  }
jjwt

jjwt可以说是把’极简‘一词给表达出来,token的生成全部一手包办。没有多余的操作余地。

    public static void main(String[] args) {
       //jjwt支持RSA-PSS算法。但是朕水不会生成PSS算法的的密钥对
       String token = Jwts.builder()
              .claim("username","zhenshui")
              .claim("isAuth","0")
              .signWith(SignatureAlgorithm.RS256, PairKey.keyPair.getPrivate())
           //.signWith(SignatureAlgorithm.HS256,"secret")
              .compact();
       //私钥加密,公钥解密
       Jwt parse = Jwts.parser()
               //设置公钥
              .setSigningKey(PairKey.keyPair.getPublic())
           //.signWith(SignatureAlgorithm.HS256,"secret")
              .parse(token);
       //得到Json数据,所有数据
       Object body = parse.getBody();
       System.out.println(body.toString());
  }
funsionauth

可以说,这个框架是令朕水最失望的框架。繁杂的操作,只能接收密钥字符串,密钥需要开始和结束标识,速度也比不上前面两个、设置头部信息需要使用函数式编程等等问题。。

    public static void main(String[] args) {
       //这是一个SMAC256的例子
       String  secret ="secret";
       //设置密钥和算法SHA256
       Signer signer = HMACSigner.newSHA256Signer(secret);
       JWT jwt = new JWT()
               //设置载荷基本信息
              .setIssuer("zhenshui")
              .addClaim("username","朕水真水");
       //对JWT进行编码
       String token = JWT.getEncoder().encode(jwt, signer,(header)->{
           header.set("key","header"); //设置头部信息
      });
       System.out.println(token);
       //校验token使用的校验密钥和算法
       Verifier verifier = HMACVerifier.newVerifier(secret);
       //进行解码(base64)
       JWT decodedJwt = JWT.getDecoder().decode(token, verifier);
       //得到所有载荷参数
       Map<String, Object> allClaims = decodedJwt.getAllClaims();
       System.out.println(allClaims.toString());
       //得到自定义参数
       Map<String, Object> otherClaims = decodedJwt.getOtherClaims();
       System.out.println(otherClaims);
       //读取某个参数名的值
       String key = decodedJwt.getString("username");
       System.out.println(key);
  }
    public static void main(String[] args) throws IOException {
       JWT jwt = new JWT()
              .setIssuer("zhenshui")
              .addClaim("username","朕水真水");
       String str = Base64.encodeBase64String(PairKey.keyPair.getPrivate().getEncoded());
       //没测试RSA成功。因为PEMDecoder的decode方法强制要求RSA的公钥私钥必须有 开始的标识和结束的标识
       // -----BEGIN RSA PRIVATE KEY-----
       //-----END RSA PRIVATE KEY-----
       RSASigner rsaSigner = RSASigner.newSHA256Signer(str);
       //对JWT进行编码
       String encodedJwt = JWT.getEncoder().encode(jwt, rsaSigner,(header)->{
           header.set("key","header");
      });

       //校验token使用的校验密钥和算法
       Verifier verifier = RSAVerifier.newVerifier((RSAPublicKey) PairKey.keyPair.getPublic());
       //进行解码(base64)
       JWT decodedJwt = JWT.getDecoder().decode(encodedJwt, verifier);
       //得到所有载荷参数
       Map<String, Object> allClaims = decodedJwt.getAllClaims();
       System.out.println(allClaims.toString());
  }

 

上面两个是funsionauth的HMAC256(对称加密算法)和RSA(非对称加密算法)两种算法的例子。如果说使用HMAC256只是复杂一点的话。那使用非对称加密之类的算法就是给朕水当头一棒。 先说说funsionauth的非对称加密的API,它里面设置私钥只能设置字符串类型。好吧,朕水忍住了,转字符串进去。拿字符串去干嘛?一看代码不得了,拿朕水给的字符串去转化为RSAPrivateKey...好吧,这是作者的设计,朕水再忍。运行一下代码,报错了。仔细看了看源码,必须要有BEGIN PRIVATE KEY.... 可能是朕水不能理解作者的思想,朕水告辞了。后续有机会再研究吧,这里朕水只做简单上手的测试。整体来说,这个框架被约束的比较厉害。

贴一下funsionauth的源码

jose4j

可以说jose4j很是最规矩的一个框架。在载荷放什么数据全看用户设置的Map中,符合规范的字段(sub、iss、jti、aud等)就调用对应的校验器进行校验,提供一个JsonUtil将Map转化为JSON格式的字符串。

public static void main(String[] args) throws JoseException {
       //自带生成RSA算法密钥工具
       //RsaJsonWebKey jwk = RsaJwkGenerator.generateJwk(1025);
       //加密解密使用的都是JsonWebSignature对象
       Map<String, String> payload = new HashMap<>();
       payload.put("username","zhenshui");
       payload.put("sub","开party!!");
       //设置载荷
       JsonWebSignature jws = new JsonWebSignature();
       jws.setPayload(JsonUtil.toJson(payload));
       //设置密钥和加密方式
       jws.setKey(PairKey.keyPair.getPrivate());
       jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256);
       //jws.setKey(new HmacKey(secret.getBytes()));
       //jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.HMAC_SHA256);
       String token = jws.getCompactSerialization();
       System.out.println(token);
       //JsonWebSignature校验
       JsonWebSignature jwsvif = new JsonWebSignature();
       //设置token
       jwsvif.setCompactSerialization(token);
       //设置公钥
       jwsvif.setKey(PairKey.keyPair.getPublic());
       //校验
       boolean v = jwsvif.verifySignature();
       //得到载荷
       if (v) {
           System.out.println(jwsvif.getPayload());
      }
  }

 

jose4j中jwe的实现简单使用。生成与校验基本与JWS使用方式一致。改为JsonWebEncryption对象生成

public static void main(String[] args) throws JoseException {
       //使用的都是JsonWebEncryption
       //加密
       JsonWebEncryption jwe = new JsonWebEncryption();
       Map<String, String> payload = new HashMap<>();
       payload.put("username","zhenshui");
       //设置载荷
       jwe.setPayload(JsonUtil.toJson(payload));
       //加密算法
       jwe.setAlgorithmHeaderValue(KeyManagementAlgorithmIdentifiers.RSA_OAEP_256);
       //消息摘要算法
       jwe.setEncryptionMethodHeaderParameter(ContentEncryptionAlgorithmIdentifiers.AES_128_CBC_HMAC_SHA_256);
       //Key key = new AesKey(secret.getBytes()); 使用对称加密算法时的Key
       jwe.setKey(PairKey.keyPair.getPublic());
       //序列化token
       String token = jwe.getCompactSerialization();
       System.out.println( token);
       //重新设置一个 对象
       jwe = new JsonWebEncryption();
       //设置两层算法
       jwe.setAlgorithmConstraints(new AlgorithmConstraints(AlgorithmConstraints.ConstraintType.PERMIT,
               KeyManagementAlgorithmIdentifiers.RSA_OAEP_256));
       jwe.setContentEncryptionAlgorithmConstraints(new AlgorithmConstraints(AlgorithmConstraints.ConstraintType.PERMIT,
               ContentEncryptionAlgorithmIdentifiers.AES_128_CBC_HMAC_SHA_256));
       //设置密钥
       jwe.setKey(PairKey.keyPair.getPrivate());
       //设置token
       jwe.setCompactSerialization(token);
       System.out.println( jwe.getPayload());
       //得到的token为5段//在请求头设置两个算法
  }
nimbusds-jose

这个框架对比jose4j来说并没有那么流畅,给朕水的感觉就是使用起来比jose4j复杂,但是能明显的感受JWT的设计理念。

public static void main(String[] args) throws MalformedURLException, ParseException, JOSEException, BadJOSEException {
       //JWSSigner jwsSigner = new MACSigner(secret); //SMAC256算法
       JWSSigner jwsSigner = new RSASSASigner(PairKey.keyPair.getPrivate());
       JWSHeader jwsHeader = new JWSHeader
              .Builder(JWSAlgorithm.RS256)
              .type(JOSEObjectType.JWT).build();
       //参数
       JWTClaimsSet jwtClaimsSet =new JWTClaimsSet.Builder()
              .issuer("admin")
              .subject("sub")
              .claim("username","zhenshui")
              .build();
       Payload payload = new Payload(jwtClaimsSet.toJSONObject());
       JWSObject jwsObject = new JWSObject(jwsHeader, payload);
       jwsObject.sign(jwsSigner);
       //base64
       String token = jwsObject.serialize();
       System.out.println(token);
       //解密
       JWSVerifier verifier = new RSASSAVerifier((RSAPublicKey) PairKey.keyPair.getPublic());
       //不校验获取头部和载荷
       JWSObject parseJWS = JWSObject.parse(token);
       //校验
       boolean verify = parseJWS.verify(verifier);
       //获取载荷
       if (verify) {
           String decryptPayload = parseJWS.getPayload().toString();
           System.out.println(decryptPayload);
      }
  }

 

在nimbus-jose中的JWE实现基本与JWS无差。改为JWEObject对象生成

    public static void main(String[] args) throws ParseException, JOSEException {
       //载荷字段
       JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
              .issuer("admin")
              .subject("sub")
              .claim("username","zhenshui")
              .build();
       //双重加密
       JWEHeader header = new JWEHeader(JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A128CBC_HS256);
       //载荷可以设置字符串
       //Payload payload = new Payload("Hello world!");
       Payload payload = new Payload(claimsSet.toJSONObject());
       JWEObject jwt = new JWEObject(header, payload);
       //加密
       RSAEncrypter encrypter = new RSAEncrypter((RSAPublicKey) PairKey.keyPair.getPublic());
       //加密
       jwt.encrypt(encrypter);
       String token = jwt.serialize();
       System.out.println(token);
       //设置RSA解密对象私钥
       RSADecrypter decrypter = new RSADecrypter(PairKey.keyPair.getPrivate());
       //解密//公钥加密私钥解密
       EncryptedJWT parseJWT = EncryptedJWT.parse(token);
       //解密
       parseJWT.decrypt(decrypter);
       //获取载荷的值
       JWTClaimsSet jwtClaimsSet = parseJWT.getJWTClaimsSet();
       System.out.println(jwtClaimsSet.toString());
       //得到的token为5段//在请求头设置两个算法
  }

 

 

JWT框架简评

朕水对5个框架进行简单的上手的使用感受给各位参考,结果如下:

上手难度:

auth0 > jjwt > jose4j > fusionauth > nimbus-jose > vertx

vertx排在最后只是朕水个人的使用感受(实际上朕水试用了一天都没有生成Token成功)。

功能完整性:

只有jose4j和nimbus-jose有JWE的相关API,其他4个没有。 只有jose4j和nimbus-jose才有密钥(非对称加密)的生成器, auth0、jjwt、fusionauth要自己生成私钥和公钥。 只有jose4j和nimbus-jose不强制载荷设置为键值对,可以设置为字符串。auth0、jjwt和funsionauth强制使用键值对。 只有auth0和jjwt没有密钥管理服务的使用API 朕水怀疑jose4j和nimbus-jose是一伙的!!!功能都非常的相似) 从上手难度、功能完整性来看,推荐使用:nimbus-jose > jose4j 推荐原因:nimbus-jose和jose4j功能是最全的。他们两个的功能相差不大,区别点:jose4j的载荷参数要用户设置,nimbus-jose可以使用JWTClaimsSet构造出来,nimbus-jose生成和解析Token速度快,不过nimbus-jose不校验subnbfiatjti 如果只使用JWS功能推荐使用:auth0 > jjwt 推荐原因:官方推荐、上手简单、文档全,轻量,生成校验token快。jjwt与auth0类似,但是jjwt则尽量不依赖外部类库,使用自己内部的写的方法。 funsionauth要哭了,毫无地位(funsionauth:喵?喵?喵?) 目前,auth0和jjwt都没有密钥管理服务的API。所以当朕水只想使用JWS的token生成和远程检索密钥的时候,朕水就只有3个选择funsionauth、nimbus-jose、jose4j。在这三个中,只有funsionauth功能没有冗余。 上面的上手感受的简评并不严谨,实际使用以项目需求为准(用不到JWE功能,JWT端点的API等功能就没必要使用nimbus-jose、jose4j),如果有误可指正。

PS:

浅谈一下关于JSON。JWT是对JSON数据根据某种加密得到的字符串。为什么朕水要把 "可以设置为字符串"归为功能完整性。主要是因为字符串也是JSON数据格式!! 朕水发觉很多同学都有一个误区,就是只有下面这种格式才是JSON格式:

{"key":"value"}

实际上JSON可以有很多种,只是常用的格式是上面的那种形式。其他的形式例如:

"" //字符串类型

["1","2","3"] //数组类型

74.74 //数字、浮点类型

 

后记

 

微信关注"朕水真水",一起学习更多后端技术。

看到发送给我的消息我尽量查看。

 

 

 

 

 

 

posted @ 2020-08-24 15:15  朕水真水  阅读(1729)  评论(0)    收藏  举报