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 //数字、浮点类型

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值