关于java:JWT框架简单测评哪款是你的菜

58次阅读

共计 11341 个字符,预计需要花费 29 分钟才能阅读完成。

温故而知新,能够为师矣。

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 不校验 `sub`、`nbf`、`iat`、`jti`。

如果只应用 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 // 浮点类型

后记

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

看到发送给我的音讯我尽量查看。

正文完
 0