JWT
把书本读薄,简单介绍一下JWT所具备的特征,最好提前了解 “编码”、“摘要” 和 “加密” 的区别。
JWT简介
JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.
JWT是一种设计令牌的开放标准。(开发标准,就意味着可以有多种实现)
JWT结构
JWT分为3段,用“.”连接,其中:“头部”和“载荷”通过Base64进行编码的,“签名”是将前2段连接在一起,通过加密算法得出的校验码。
HEADER(头部).PAYLOAD(载荷).SIGNATURE(签名)
头部
Base64解码后是一段JSON,用于说明jwt所采用的算法
alg: HS256
typ: JWT
载荷
Base64解码后是一段JSON,用于存储jwt的详细信息,字段只是推荐,并没有强制要求这些字段必须存在
iss:发行人
exp:token的到期时间
sub:主题
aud:用户
nbf:token在此之前不可用
iat:发布时间
jti:JWT ID用于标识该JWT
签名
“头部” 和 “载荷” 使用Base64编码,编码并不是加密,用户可以随时解码,查看里面的内容。
允许用户看 token 的内容,又不允许他们修改,这就是 “签名” 的作用:防止用户伪造token。
比如:token 过期了,用户偷偷修改了 token 的有效期,这时候,服务器只要把数据再加密一遍,如果加密的结果不一致,就说明token被修改过。
(这里说的加密,可以是摘要算法,也可以使加密算法)
DEMO
自带的加密算法有这么多:none、HS256、HS384、HS512、RS256、RS384、RS512、ES256、ES384、ES512、PS256、PS384、PS512。
挑2个展示效果,对称加密HS256,非对称加密RS256。
Maven依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.17.0</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
HS256
对称加密,加密和解密需要相同的密钥。
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import javax.crypto.SecretKey;
import java.util.Base64;
import java.util.Date;
/**
* @author Mr.css
* @date 2022-01-05 15:03
*/
public class JwtHS256 {
// /**
// * 生成密钥
// *
// * @param key 长度必须是32的倍数
// * @return SecretKey
// * @throws IllegalArgumentException -
// */
// public static SecretKey generateSecretKey(byte[] key) {
// if (key.length % 32 != 0) {
// throw new IllegalArgumentException("key size must be a multiple of 32");
// }
// return new SecretKeySpec(key, "AES");
// }
/**
* 密钥,更复杂地,应该将密钥交由数据库管理
*/
private static SecretKey secretKey = AES.generateSecretKey(MD5.hexBit32("this is a secret key!").getBytes());
/**
* 生成jwt
*
* @param id ID
* @param subject 主题
* @param ttlMillis 超时时间
* @return jwt
*/
public static String encrypt(String id, String subject, long ttlMillis) {
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
JwtBuilder builder = Jwts.builder();
//ID
builder.setId(id);
//主题
builder.setSubject(subject);
// 签发者
builder.setIssuer("system");
// 签发时间
builder.setIssuedAt(now);
// 签名算法以及密匙
builder.signWith(SignatureAlgorithm.HS256, secretKey);
// 设置超时时间
if (ttlMillis >= 0) {
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
builder.setExpiration(expDate);
}
return builder.compact();
}
/**
* 解析JWT字符串
*
* @param jwt jwt
* @return Claims
*/
public static Claims decrypt(String jwt) {
return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody();
}
public static void main(String[] args) throws InterruptedException {
//解密
String jwt = encrypt("id", "sub", 3000);
System.out.println(jwt);
//解密
Claims claims = decrypt(jwt);
System.out.println(claims);
//解析头部
System.out.println(
new String(Base64.getDecoder().decode("eyJhbGciOiJIUzI1NiJ9")));
//解析载荷
System.out.println(
new String(Base64.getDecoder().decode("eyJqdGkiOiJpZCIsInN1YiI6InN1YiIsImlzcyI6InN5c3RlbSIsImlhdCI6MTY0MTM3NTc3MywiZXhwIjoxNjQxMzc1Nzc2fQ")));
//5秒之后token失效
Thread.sleep(5000);
claims = decrypt(jwt);
System.out.println(claims);
}
}
RS256
代码上与HS256基本一致。JWT限定了只能用私钥加密,公钥解密。
什么情况下使用RS256?
令牌由其它服务器发出,我们的服务器只负责调用,我们在使用令牌之前,需要验证令牌是可信任的。
(比如:微服务,一个服务器只负责造令牌,其它负责用,使用之前,需要保证令牌是自己人发出来的。)
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.security.*;
import java.util.Date;
/**
* @author Mr.css
* @date 2022-01-05 15:03
*/
public class JwtRS256 {
/**
* 公钥/私钥一般交由数据库管理
*/
private static PublicKey publicKey;
private static PrivateKey privateKey;
// /**
// * 获取密钥对
// *
// * @param initialize 长度,1024
// * @return 密钥对
// */
// public static KeyPair getKeyPair(int initialize) throws GeneralSecurityException {
// KeyPairGenerator keyPairGen;
// keyPairGen = KeyPairGenerator.getInstance("RSA");
// keyPairGen.initialize(initialize);
// return keyPairGen.generateKeyPair();
// }
static {
try {
KeyPair keyPair = RSA.getKeyPair();
publicKey = keyPair.getPublic();
privateKey = keyPair.getPrivate();
} catch (GeneralSecurityException e) {
e.printStackTrace();
}
}
/**
* 生成jwt
*
* @param id ID
* @param subject 主题
* @param ttlMillis 超时时间
* @return jwt
*/
public static String encrypt(String id, String subject, long ttlMillis) {
Date now = new Date();
JwtBuilder builder = Jwts.builder();
//ID
builder.setId(id);
//主题
builder.setSubject(subject);
// 签发者
builder.setIssuer("system");
// 签发时间
builder.setIssuedAt(now);
// 签名算法以及密匙
builder.signWith(SignatureAlgorithm.RS256, privateKey);
// 设置超时时间
if (ttlMillis >= 0) {
long expMillis = now.getTime() + ttlMillis;
Date expDate = new Date(expMillis);
builder.setExpiration(expDate);
}
return builder.compact();
}
/**
* 解析JWT字符串
*
* @param jwt jwt
* @return Claims
*/
public static Claims decrypt(String jwt) {
return Jwts.parser().setSigningKey(publicKey).parseClaimsJws(jwt).getBody();
}
public static void main(String[] args) throws InterruptedException {
//加密
String jwt = encrypt("id", "sub", 3000);
System.out.println(jwt);
//解密
Claims claims = decrypt(jwt);
System.out.println(claims);
//暂停5秒再解密,超时报错
Thread.sleep(5000);
claims = decrypt(jwt);
System.out.println(claims);
}
}
公钥/私钥——字符串和对象的转换
/**
* 获取公钥字符串
*/
public static String getPublicKey(KeyPair keyPair) {
return Base64.encodeString(keyPair.getPublic().getEncoded());
}
/**
* 获取私钥字符串
*/
public static String getPrivateKey(KeyPair keyPair) {
return Base64.encodeString(keyPair.getPrivate().getEncoded());
}
/**
* 获取公钥对象
*
* @param publicKey 公钥字符串
* @return 公钥
* @throws GeneralSecurityException -
*/
public static PublicKey generatePublicKey(String publicKey) throws GeneralSecurityException {
byte[] keyBytes = Base64.decodeString(publicKey);
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory factory = KeyFactory.getInstance("RSA");
return factory.generatePublic(x509EncodedKeySpec);
}
/**
* 获取私钥对象
*
* @param privateKey 私钥字符串
* @return 私钥
* @throws GeneralSecurityException -
*/
public static PrivateKey generatePrivateKey(String privateKey) throws GeneralSecurityException {
byte[] keyBytes = Base64.decodeString(privateKey);
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory factory = KeyFactory.getInstance("RSA");
return factory.generatePrivate(pkcs8EncodedKeySpec);
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY