在 JWT(JSON Web Token)中,涉及到两种主要的算法类型:加密算法和签名算法。
签名算法用于确保 JWT 数据的完整性和真实性,必须使用;而加密算法用于保护 JWT 内容的机密性,选择性使用。这里看下签名算法。
签名算法的主要目的是确保 JWT 的内容未被篡改,并验证其来源。签名算法用于生成一个数字签名,接收方使用相同的算法来验证签名,从而确认数据的完整性。
不同的jwt插件有不同的加密算法名称,原理都一样
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.auth0.jwt.algorithms;
import com.auth0.jwt.exceptions.SignatureGenerationException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.ECDSAKeyProvider;
import com.auth0.jwt.interfaces.RSAKeyProvider;
import java.security.interfaces.ECKey;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
public abstract class Algorithm {
private final String name;
private final String description;
public static Algorithm RSA256(RSAKeyProvider keyProvider) throws IllegalArgumentException {
return new RSAAlgorithm("RS256", "SHA256withRSA", keyProvider);
}
public static Algorithm RSA256(RSAPublicKey publicKey, RSAPrivateKey privateKey) throws IllegalArgumentException {
return RSA256(RSAAlgorithm.providerForKeys(publicKey, privateKey));
}
/** @deprecated */
@Deprecated
public static Algorithm RSA256(RSAKey key) throws IllegalArgumentException {
RSAPublicKey publicKey = key instanceof RSAPublicKey ? (RSAPublicKey)key : null;
RSAPrivateKey privateKey = key instanceof RSAPrivateKey ? (RSAPrivateKey)key : null;
return RSA256(publicKey, privateKey);
}
public static Algorithm RSA384(RSAKeyProvider keyProvider) throws IllegalArgumentException {
return new RSAAlgorithm("RS384", "SHA384withRSA", keyProvider);
}
public static Algorithm RSA384(RSAPublicKey publicKey, RSAPrivateKey privateKey) throws IllegalArgumentException {
return RSA384(RSAAlgorithm.providerForKeys(publicKey, privateKey));
}
/** @deprecated */
@Deprecated
public static Algorithm RSA384(RSAKey key) throws IllegalArgumentException {
RSAPublicKey publicKey = key instanceof RSAPublicKey ? (RSAPublicKey)key : null;
RSAPrivateKey privateKey = key instanceof RSAPrivateKey ? (RSAPrivateKey)key : null;
return RSA384(publicKey, privateKey);
}
public static Algorithm RSA512(RSAKeyProvider keyProvider) throws IllegalArgumentException {
return new RSAAlgorithm("RS512", "SHA512withRSA", keyProvider);
}
public static Algorithm RSA512(RSAPublicKey publicKey, RSAPrivateKey privateKey) throws IllegalArgumentException {
return RSA512(RSAAlgorithm.providerForKeys(publicKey, privateKey));
}
/** @deprecated */
@Deprecated
public static Algorithm RSA512(RSAKey key) throws IllegalArgumentException {
RSAPublicKey publicKey = key instanceof RSAPublicKey ? (RSAPublicKey)key : null;
RSAPrivateKey privateKey = key instanceof RSAPrivateKey ? (RSAPrivateKey)key : null;
return RSA512(publicKey, privateKey);
}
public static Algorithm HMAC256(String secret) throws IllegalArgumentException {
return new HMACAlgorithm("HS256", "HmacSHA256", secret);
}
public static Algorithm HMAC384(String secret) throws IllegalArgumentException {
return new HMACAlgorithm("HS384", "HmacSHA384", secret);
}
public static Algorithm HMAC512(String secret) throws IllegalArgumentException {
return new HMACAlgorithm("HS512", "HmacSHA512", secret);
}
public static Algorithm HMAC256(byte[] secret) throws IllegalArgumentException {
return new HMACAlgorithm("HS256", "HmacSHA256", secret);
}
public static Algorithm HMAC384(byte[] secret) throws IllegalArgumentException {
return new HMACAlgorithm("HS384", "HmacSHA384", secret);
}
public static Algorithm HMAC512(byte[] secret) throws IllegalArgumentException {
return new HMACAlgorithm("HS512", "HmacSHA512", secret);
}
public static Algorithm ECDSA256(ECDSAKeyProvider keyProvider) throws IllegalArgumentException {
return new ECDSAAlgorithm("ES256", "SHA256withECDSA", 32, keyProvider);
}
public static Algorithm ECDSA256(ECPublicKey publicKey, ECPrivateKey privateKey) throws IllegalArgumentException {
return ECDSA256(ECDSAAlgorithm.providerForKeys(publicKey, privateKey));
}
/** @deprecated */
@Deprecated
public static Algorithm ECDSA256(ECKey key) throws IllegalArgumentException {
ECPublicKey publicKey = key instanceof ECPublicKey ? (ECPublicKey)key : null;
ECPrivateKey privateKey = key instanceof ECPrivateKey ? (ECPrivateKey)key : null;
return ECDSA256(publicKey, privateKey);
}
public static Algorithm ECDSA384(ECDSAKeyProvider keyProvider) throws IllegalArgumentException {
return new ECDSAAlgorithm("ES384", "SHA384withECDSA", 48, keyProvider);
}
public static Algorithm ECDSA384(ECPublicKey publicKey, ECPrivateKey privateKey) throws IllegalArgumentException {
return ECDSA384(ECDSAAlgorithm.providerForKeys(publicKey, privateKey));
}
/** @deprecated */
@Deprecated
public static Algorithm ECDSA384(ECKey key) throws IllegalArgumentException {
ECPublicKey publicKey = key instanceof ECPublicKey ? (ECPublicKey)key : null;
ECPrivateKey privateKey = key instanceof ECPrivateKey ? (ECPrivateKey)key : null;
return ECDSA384(publicKey, privateKey);
}
public static Algorithm ECDSA512(ECDSAKeyProvider keyProvider) throws IllegalArgumentException {
return new ECDSAAlgorithm("ES512", "SHA512withECDSA", 66, keyProvider);
}
public static Algorithm ECDSA512(ECPublicKey publicKey, ECPrivateKey privateKey) throws IllegalArgumentException {
return ECDSA512(ECDSAAlgorithm.providerForKeys(publicKey, privateKey));
}
/** @deprecated */
@Deprecated
public static Algorithm ECDSA512(ECKey key) throws IllegalArgumentException {
ECPublicKey publicKey = key instanceof ECPublicKey ? (ECPublicKey)key : null;
ECPrivateKey privateKey = key instanceof ECPrivateKey ? (ECPrivateKey)key : null;
return ECDSA512(publicKey, privateKey);
}
public static Algorithm none() {
return new NoneAlgorithm();
}
protected Algorithm(String name, String description) {
this.name = name;
this.description = description;
}
public String getSigningKeyId() {
return null;
}
public String getName() {
return this.name;
}
String getDescription() {
return this.description;
}
public String toString() {
return this.description;
}
public abstract void verify(DecodedJWT var1) throws SignatureVerificationException;
public byte[] sign(byte[] headerBytes, byte[] payloadBytes) throws SignatureGenerationException {
byte[] contentBytes = new byte[headerBytes.length + 1 + payloadBytes.length];
System.arraycopy(headerBytes, 0, contentBytes, 0, headerBytes.length);
contentBytes[headerBytes.length] = 46;
System.arraycopy(payloadBytes, 0, contentBytes, headerBytes.length + 1, payloadBytes.length);
return this.sign(contentBytes);
}
/** @deprecated */
@Deprecated
public abstract byte[] sign(byte[] var1) throws SignatureGenerationException;
}
io.jsonwebtoken.SignatureAlgorithm
NONE("none", "No digital signature or MAC performed", "None", (String)null, false, 0, 0),
HS256("HS256", "HMAC using SHA-256", "HMAC", "HmacSHA256", true, 256, 256, "1.2.840.113549.2.9"),
HS384("HS384", "HMAC using SHA-384", "HMAC", "HmacSHA384", true, 384, 384, "1.2.840.113549.2.10"),
HS512("HS512", "HMAC using SHA-512", "HMAC", "HmacSHA512", true, 512, 512, "1.2.840.113549.2.11"),
RS256("RS256", "RSASSA-PKCS-v1_5 using SHA-256", "RSA", "SHA256withRSA", true, 256, 2048),
RS384("RS384", "RSASSA-PKCS-v1_5 using SHA-384", "RSA", "SHA384withRSA", true, 384, 2048),
RS512("RS512", "RSASSA-PKCS-v1_5 using SHA-512", "RSA", "SHA512withRSA", true, 512, 2048),
ES256("ES256", "ECDSA using P-256 and SHA-256", "ECDSA", "SHA256withECDSA", true, 256, 256),
ES384("ES384", "ECDSA using P-384 and SHA-384", "ECDSA", "SHA384withECDSA", true, 384, 384),
ES512("ES512", "ECDSA using P-521 and SHA-512", "ECDSA", "SHA512withECDSA", true, 512, 521),
PS256("PS256", "RSASSA-PSS using SHA-256 and MGF1 with SHA-256", "RSA", "RSASSA-PSS", false, 256, 2048),
PS384("PS384", "RSASSA-PSS using SHA-384 and MGF1 with SHA-384", "RSA", "RSASSA-PSS", false, 384, 2048),
PS512("PS512", "RSASSA-PSS using SHA-512 and MGF1 with SHA-512", "RSA", "RSASSA-PSS", false, 512, 2048);
HMAC类属于对称加密;
这些算法使用同一个密钥(secret_key)进行签名和验证,属于对称加密。一旦密钥泄露,安全性就会受到威胁。适用于信任的系统内部,例如单体应用或集中式认证服务。
RSA类和ECDSA类算法属于非对称加密;
使用一对密钥,公钥用于验证,私钥用于签名,提供了更好的密钥管理和分发机制,即使公钥被泄露,只要私钥保持安全,Token依然是安全的。适用于分布式系统和多服务环境。
如我们项目曾经将HS256升级为ES256。
比较:
1)性能:
对称加密算法通常比非对称加密算法更快,但在分布式系统中,非对称加密算法(尤其是ECDSA)更受欢迎,因为它们提供了更好的安全性和灵活性。如我们项目曾将jwt由HS256(HMAC + SHA-256)升级为ES256(ECDSA + SHA-256)。
2)Token大小:
非对称加密算法生成的签名通常比对称加密算法的签名要大,但ECDSA算法由于其较短的签名长度,可以在保持安全性的同时减少Token的大小。在选择JWT的加密算法时,需要根据系统的具体需求和安全要求来决定使用哪种算法。对于需要跨多个服务或不信任环境传输的Token,推荐使用非对称加密算法,如RS256或ES256。
HS256算法使用了HMAC-SHA256算法,其中HMAC代表散列消息认证码,SHA256是一种散列函数,能够为任意长度的数据生成一个固定长度的摘要。
HS256的工作原理是:
使用一个密钥和一个消息作为输入;使用SHA-256散列函数来计算消息的摘要;使用HMAC(散列消息认证码)函数,结合散列函数和密钥,来生成一个密钥相关的摘要。
package com.demo.security.util;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.demo.security.constant.UserConstants;
import com.demo.security.entity.UserEntity;
import lombok.extern.slf4j.Slf4j;
import java.util.Date;
import java.util.Map;
@Slf4j
public class JwtUtilTest {
public static void main(String[] args) {
//1、生成token
String username = "zs";
Integer age = 18;
String key = "123456";
Algorithm algorithm = Algorithm.HMAC256(key);
UserEntity userEntity = UserEntity.builder()
.username(username)
.age(age)
.build();
String token = getToken(userEntity,algorithm);
log.info("token获取成功:{}",token);
log.info("token长度:{}",token.length());
//2、验证token,因为只要token是jwt,第三步一定会成功,这里是为了验证是不是我生成的,防止攻击
JWTVerifier verifier = JWT.require(algorithm).build();
verifier.verify(token);
//3、解析token
UserEntity tokenUser = getUser(token);
if(username.equals(tokenUser.getUsername())){
log.info("解析成功");
}
}
public static UserEntity getUser( String token) {
try {
DecodedJWT jwt = JWT.decode(token);
Map claims = jwt.getClaims();
String username = claims.get("username").asString();
int age = claims.get("age").asInt();
return UserEntity.builder()
.username(username)
.age(age)
.build();
} catch (JWTDecodeException e) {
return null;
}
}
/**
* 生成签名,5min(分钟)后过期
* @return 加密的token
*/
public static String getToken(UserEntity userEntity, Algorithm algorithm) {
Date date = new Date(System.currentTimeMillis() + UserConstants.EXPIRE_TIME);
// 附带username信息
return JWT.create()
//iss:签发方
.withIssuer("wtyy")
//aud:接收jwt的一方
.withAudience("web-demo")
//exp:jwt的过期时间,这个过期时间必须要大于签发时间
//.withExpiresAt()
//其他自定义通信信息
.withClaim("username", userEntity.getUsername())
.withClaim("age",userEntity.getAge())
.withExpiresAt(date)
.sign(algorithm);
}
}
执行打印
14:50:25.610 [main] INFO com.demo.security.util.JwtUtilTest -- token获取成功:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJ3ZWItZGVtbyIsImlzcyI6Ind0eXkiLCJleHAiOjE3MjEzNzM2MjUsImFnZSI6MTgsInVzZXJuYW1lIjoienMifQ.9dEWE3Q6zBa6szo1QVv5UaGfeXMsWyCJ64RSnWFx1Ns
14:50:25.624 [main] INFO com.demo.security.util.JwtUtilTest -- token长度:179
14:50:25.645 [main] INFO com.demo.security.util.JwtUtilTest -- 解析成功
通过在线解析
这里为了证明第二步verify的重要性,改下代码:
public static void main(String[] args) {
//1、生成token
String username = "zs";
Integer age = 18;
String key = "123456";
Algorithm algorithm = Algorithm.HMAC256(key);
UserEntity userEntity = UserEntity.builder()
.username(username)
.age(age)
.build();
String token = getToken(userEntity,algorithm);
log.info("token获取成功:{}",token);
log.info("token长度:{}",token.length());
//2、验证token,因为只要token是jwt,第三步一定会成功,这里是为了验证是不是我生成的,防止攻击
try{
Algorithm errorAlgorithm = Algorithm.HMAC256(key+"error");
JWTVerifier verifier = JWT.require(errorAlgorithm).build();
verifier.verify(token);
}catch (Exception e){
//throw异常
log.error("验证失败");
}
//3、解析token
UserEntity tokenUser = getUser(token);
if(username.equals(tokenUser.getUsername())){
log.info("解析成功,name={}",tokenUser.getUsername());
}
}
打印:
15:48:42.922 [main] INFO com.demo.security.util.JwtUtilTest -- token获取成功:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJ3ZWItZGVtbyIsImlzcyI6Ind0eXkiLCJleHAiOjE3MjEzNzcxMjIsImFnZSI6MTgsInVzZXJuYW1lIjoienMifQ.mVQ5cO-nPo1SSw0qhXFdDbjQK4WdKSLmLjFWSkt8fQQ
15:48:42.924 [main] INFO com.demo.security.util.JwtUtilTest -- token长度:179
15:48:42.943 [main] ERROR com.demo.security.util.JwtUtilTest -- 验证失败
15:48:42.944 [main] INFO com.demo.security.util.JwtUtilTest -- 解析成功,name=zs
使用SHA-384算法进行HMAC签名。
上面的代码修改为
Algorithm algorithm = Algorithm.HMAC384(secret);
执行打印
14:55:46.167 [main] INFO com.demo.security.util.JwtUtilTest -- token获取成功:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzM4NCJ9.eyJhdWQiOiJ3ZWItZGVtbyIsImlzcyI6Ind0eXkiLCJleHAiOjE3MjEzNzM5NDYsImFnZSI6MTgsInVzZXJuYW1lIjoienMifQ.wB0LaglQH_IcFKHa7oLm5r2Gof2DzCS0XmhvYC0m9q5nk4T4-CCTCpGllyc3waPR
14:55:46.167 [main] INFO com.demo.security.util.JwtUtilTest -- token长度:200
14:55:46.183 [main] INFO com.demo.security.util.JwtUtilTest -- 解析成功
使用SHA-512算法进行HMAC签名。
上面的代码修改为
Algorithm algorithm = Algorithm.HMAC512(secret);
执行输出:
14:57:29.979 [main] INFO com.demo.security.util.JwtUtilTest -- token获取成功:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJhdWQiOiJ3ZWItZGVtbyIsImlzcyI6Ind0eXkiLCJleHAiOjE3MjEzNzQwNDksImFnZSI6MTgsInVzZXJuYW1lIjoienMifQ.J7-tJFmjP6Pnh9djMI6MnAEIFKK0Vvzn-ASVFRTWUf3kHa-NDOmYydP4jRDitui2EJNfxLu4taOo5YWZXYJoSQ
14:57:29.979 [main] INFO com.demo.security.util.JwtUtilTest -- token长度:222
14:57:29.994 [main] INFO com.demo.security.util.JwtUtilTest -- 解析成功
使用RSA和SHA-256算法进行签名。
上面的代码修改为:
package com.demo.security.util;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.demo.security.constant.UserConstants;
import com.demo.security.entity.UserEntity;
import lombok.extern.slf4j.Slf4j;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.Map;
@Slf4j
public class JwtUtilTest {
public static void main(String[] args) throws Exception {
//1、先生成公钥私钥
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048);
KeyPair pair = keyGen.generateKeyPair();
RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) pair.getPrivate();
RSAPublicKey rsaPublicKey = (RSAPublicKey) pair.getPublic();
//2、根据私钥生成jwt
String username = "zs";
Integer age = 18;
UserEntity userEntity = UserEntity.builder()
.username(username)
.age(age)
.build();
String token = getToken(userEntity,rsaPrivateKey,rsaPublicKey);
log.info("token获取成功:{}",token);
log.info("token长度:{}",token.length());
//3、先验证是否合法,注意第四步是一定可以成功的,这里是为了安全验证,校验token是不是我生成的,防止攻击
Algorithm algorithm = Algorithm.RSA256(rsaPublicKey, null);
JWTVerifier verifier = JWT.require(algorithm).build();
//公钥验证不通过会抛异常
verifier.verify(token);
//log.info("验证成功");
//4、根据公钥解析jwt
UserEntity tokenUser = getUser(token);
if(username.equals(tokenUser.getUsername())){
log.info("解析成功");
}
}
public static UserEntity getUser( String token) {
try {
DecodedJWT jwt = JWT.decode(token);
Map claims = jwt.getClaims();
String username = claims.get("username").asString();
int age = claims.get("age").asInt();
return UserEntity.builder()
.username(username)
.age(age)
.build();
} catch (JWTDecodeException e) {
return null;
}
}
/**
* 生成签名,5min(分钟)后过期
* @param rsaPrivateKey
* @param rsaPublicKey
* @return 加密的token
*/
public static String getToken(UserEntity userEntity, RSAPrivateKey rsaPrivateKey, RSAPublicKey rsaPublicKey) throws Exception {
Date date = new Date(System.currentTimeMillis() + UserConstants.EXPIRE_TIME);
Algorithm algorithm = Algorithm.RSA256(rsaPublicKey,rsaPrivateKey);
// 附带username信息
return JWT.create()
//iss:签发方
.withIssuer("wtyy")
//aud:接收jwt的一方
.withAudience("web-demo")
//exp:jwt的过期时间,这个过期时间必须要大于签发时间
//.withExpiresAt()
//其他自定义通信信息
.withClaim("username", userEntity.getUsername())
.withClaim("age",userEntity.getAge())
.withExpiresAt(date)
.sign(algorithm);
}
private static RSAPublicKey convertStringToRSAPublicKey(String publicKeyString) throws Exception {
publicKeyString = publicKeyString.replaceAll("\\n", "")
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "");
// base64 decode
byte[] publicKeyBytes = Base64.getDecoder().decode(publicKeyString);
// X509EncodedKeySpec change to PublicKey
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(keySpec);
// change PublicKey to RSAPublicKey
return (RSAPublicKey) publicKey;
}
}
执行打印
15:39:47.174 [main] INFO com.demo.security.util.JwtUtilTest -- token获取成功:eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiJ3ZWItZGVtbyIsImlzcyI6Ind0eXkiLCJleHAiOjE3MjEzNzY1ODcsImFnZSI6MTgsInVzZXJuYW1lIjoienMifQ.ERWm_kHMzFyEnrgaRC1TX19tmdalidKZ3h-SSrRkMuMmvYghFSsKWkIpvrhcXN-vtigweUThjg7IbHvS8unWV11Z6eb8vHldlq6kXnrTl802oEQqr6ORRnMafmxqEX7KqQsv7vv0JDwLbf8xEM4gebJqU_fKBOtw5mKivKt2fmZXs4vrBFHU_OXex4cUZp1TdMEAhJeysRhFGcLHqctgTkqx7NIUj80cODTMPfv2HNUJu-nZ5vn5oCmOkhD9yB5A3lpc7DM_qDL57jRQazoQMyTg97P9p_5x5qzI4mQNEqlnJyhJ9U-sqVsyFka_JiehyHJn_CEtpR031MFw0mUd-g
15:39:47.176 [main] INFO com.demo.security.util.JwtUtilTest -- token长度:478
15:39:47.199 [main] INFO com.demo.security.util.JwtUtilTest -- 解析成功
为了验证第三步的重要性,改下代码:
//3、先验证是否合法,注意第四步是一定可以成功的,这里是为了安全验证,校验token是不是我生成的,防止攻击
try{
Algorithm algorithm = Algorithm.RSA256(null, null);
JWTVerifier verifier = JWT.require(algorithm).build();
//公钥验证不通过会抛异常
verifier.verify(token);
}catch (Exception e){
log.error("验证失败");
}
//4、根据公钥解析jwt
UserEntity tokenUser = getUser(token);
if(username.equals(tokenUser.getUsername())){
log.info("解析成功,name={}",tokenUser.getUsername());
}
这两步打印:
15:42:19.882 [main] ERROR com.demo.security.util.JwtUtilTest -- 验证失败
15:42:19.905 [main] INFO com.demo.security.util.JwtUtilTest -- 解析成功,name=zs
使用RSA和SHA-384算法进行签名。
上面的代码修改下,256改成384,执行下输出
15:51:33.549 [main] INFO com.demo.security.util.JwtUtilTest -- token获取成功:eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzM4NCJ9.eyJhdWQiOiJ3ZWItZGVtbyIsImlzcyI6Ind0eXkiLCJleHAiOjE3MjEzNzcyOTMsImFnZSI6MTgsInVzZXJuYW1lIjoienMifQ.lWIWB72Xnm3Gxdoa6TTM2g7SrgDY3m3sn1TShQPJrpyXm_Z9pnv9qup3yAvUmGyR2HbsqsjX9mG6kMz5A3Rxuvl2icIA0TzSq3SD4X0CWg6yOa1CO76W9yEHvFewPkYNfDNx1gHwcmyLW4PavcZaOnpMrOfILaVoqUeoaJ9d5fye39ASLbsgj76JL1KdX7EaM3eb_x3DPp7zt46rsV76z5IGlXnxAch4va5zF3h73Pj6HmbQ6g9FDCPa9Kz3Yd9PlxKm9lWTDMTNrEZ0Dphgi7mxs2XLJoYIfJEY9_jqLeFkl2otFLTAURoS_sNrOvQMtXRkDPoraIi5gfM-IyUnJA
15:51:33.551 [main] INFO com.demo.security.util.JwtUtilTest -- token长度:478
15:51:33.573 [main] INFO com.demo.security.util.JwtUtilTest -- 解析成功
使用RSA和SHA-512算法进行签名。
上面的代码修改下,384改成512,执行下输出:
15:52:24.816 [main] INFO com.demo.security.util.JwtUtilTest -- token获取成功:eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzUxMiJ9.eyJhdWQiOiJ3ZWItZGVtbyIsImlzcyI6Ind0eXkiLCJleHAiOjE3MjEzNzczNDQsImFnZSI6MTgsInVzZXJuYW1lIjoienMifQ.sC5D9qDnJg4fvtg3M9MSlJqZ2K7t88yFaP4-KWLyd5Y5Tm-PlwJImLtT3g2NwW-NGnmUpVZ0nYY47io49FN7IYKRv7NxpniAwWOc-JBJM1o2SeGAsG_dPBfdOxB4_6f1V5gMiDuqHIdyjW7DPl3w72j2-etjXg4xBG0ed-LihzZ7MWhWreEoPoBSJja8CDalE0bXAAHQn3EKvlbAlTO7QLdOpJvM1cQJlBMTzaes8sXbc-yqY-lQ7LOtKbqdd90d_muBUq5Qn4bjO5GWcq-QGTjzSXvQH-ttKJliVblvJYpCMs0mAFIniL41lKTNNVg3G-zRcFJtwtdOUSYMEadEIA
15:52:24.818 [main] INFO com.demo.security.util.JwtUtilTest -- token长度:478
15:52:24.839 [main] INFO com.demo.security.util.JwtUtilTest -- 解析成功
特点:与RSA算法类似,ECDSA也是非对称加密算法,但使用的是椭圆曲线加密,通常比RSA算法更快,签名和验证过程更高效。同时,ECDSA的密钥长度较短,有助于减少Token的大小。
使用椭圆曲线加密算法ECDSA和SHA-256进行签名。
demo:
package com.demo.security.util;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.demo.security.constant.UserConstants;
import com.demo.security.entity.UserEntity;
import lombok.extern.slf4j.Slf4j;
import java.security.*;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.Map;
@Slf4j
public class JwtUtilTest {
public static void main(String[] args) throws Exception {
//1、先生成公钥私钥
// 初始化KeyPairGenerator
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC");
// 设置ECDSA使用的椭圆曲线为secp256r1
ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256r1");
keyPairGenerator.initialize(ecSpec);
KeyPair pair = keyPairGenerator.generateKeyPair();
ECPrivateKey ecPrivateKey = (ECPrivateKey) pair.getPrivate();
ECPublicKey ecPublicKey = (ECPublicKey) pair.getPublic();
//2、根据私钥生成jwt
String username = "zs";
Integer age = 18;
UserEntity userEntity = UserEntity.builder()
.username(username)
.age(age)
.build();
String token = getToken(userEntity,ecPrivateKey,ecPublicKey);
log.info("token获取成功:{}",token);
log.info("token长度:{}",token.length());
//3、先验证是否合法,注意第四步是一定可以成功的,这里是为了安全验证,校验token是不是我生成的,防止攻击
Algorithm algorithm = Algorithm.ECDSA256(ecPublicKey, null);
JWTVerifier verifier = JWT.require(algorithm).build();
//公钥验证不通过会抛异常
verifier.verify(token);
log.info("验证成功");
//4、根据公钥解析jwt
UserEntity tokenUser = getUser(token);
if(username.equals(tokenUser.getUsername())){
log.info("解析成功");
}
}
public static UserEntity getUser( String token) {
try {
DecodedJWT jwt = JWT.decode(token);
Map claims = jwt.getClaims();
String username = claims.get("username").asString();
int age = claims.get("age").asInt();
return UserEntity.builder()
.username(username)
.age(age)
.build();
} catch (JWTDecodeException e) {
return null;
}
}
/**
* 生成签名,5min(分钟)后过期
* @param rsaPrivateKey
* @param rsaPublicKey
* @return 加密的token
*/
public static String getToken(UserEntity userEntity, ECPrivateKey rsaPrivateKey, ECPublicKey rsaPublicKey) throws Exception {
Date date = new Date(System.currentTimeMillis() + UserConstants.EXPIRE_TIME);
Algorithm algorithm = Algorithm.ECDSA256(rsaPublicKey,rsaPrivateKey);
// 附带username信息
return JWT.create()
//iss:签发方
.withIssuer("wtyy")
//aud:接收jwt的一方
.withAudience("web-demo")
//exp:jwt的过期时间,这个过期时间必须要大于签发时间
//.withExpiresAt()
//其他自定义通信信息
.withClaim("username", userEntity.getUsername())
.withClaim("age",userEntity.getAge())
.withExpiresAt(date)
.sign(algorithm);
}
}
执行输出:
16:00:37.510 [main] INFO com.demo.security.util.JwtUtilTest -- token获取成功:eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJ3ZWItZGVtbyIsImlzcyI6Ind0eXkiLCJleHAiOjE3MjEzNzc4MzcsImFnZSI6MTgsInVzZXJuYW1lIjoienMifQ.niLzXGc3kuiYm75UrbCtK9qUTdDIUyo89nLkb1tOyH1dE--G_IILabMFsOlYHgTdx-z_zoJFjHqtlaIYXXdD8w
16:00:37.512 [main] INFO com.demo.security.util.JwtUtilTest -- token长度:222
16:00:37.538 [main] INFO com.demo.security.util.JwtUtilTest -- 验证成功
16:00:37.540 [main] INFO com.demo.security.util.JwtUtilTest -- 解析成功
通过在线解析:
使用ECDSA和SHA-384进行签名。
使用ECDSA和SHA-512进行签名。