JWT
JWT
概述
Json Web Token,通过数字签名的方式,以Json对象为载体,在不同的服务器终端之间安全地传输信息
JWT的本质就是一个字符串,它是将用户信息保存到一个Json字符串中,然后进行编码后得到一个JWT token,并且这个JWT token带有签名信息,接收后可以校验是否被篡改,所以可以用于在各方之间安全地将信息作为Json对象传输。
服务器应用在接受到JWT后,会首先对头部和载荷的内容用同一算法再次签名,如果服务器应用对头部和载荷再次以同样方法签名之后发现,自己计算出来的签名和接受到的签名不一样,那么就说明这个Token的内容被别人动过的,我们应该拒绝这个Token,返回一个HTTP 401 Unauthorized响应。
组成
JWT由三部分组成:标头(Header)、有效载荷(Payload)和签名(Signature)。在传输的时候,会将JWT的3部分分别进行Base64编码后用.
进行连接形成最终传输的字符串
-
Header
JWT头是一个描述JWT元数据的JSON对象,alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256);typ属性表示令牌的类型,JWT令牌统一写为JWT。最后,使用Base64 URL算法将上述JSON对象转换为字符串保存。
注意:
HS256 属于对称加密算法,加密和解密公用一个密钥;RS256属于非对称加密,需要通过公钥和密钥来进行加解密。
{ 'typ':'JWT', 'alg':'HS256' }
-
Payload
有效载荷部分,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。 JWT指定七个默认字段供选择。
iss:发行人
exp:到期时间
sub:主题
aud:用户
nbf:在此之前不可用
iat:发布时间
jti:JWT ID用于标识该JWT
除以上默认字段外,我们还可以自定义私有字段
{ "name":"levi", "admin":true }
请注意,默认情况下JWT是未加密的,因为只是采用base64算法,拿到JWT字符串后可以转换回原本的JSON数据,任何人都可以解读其内容,因此不要构建隐私信息字段,比如用户的密码一定不能保存到JWT中,以防止信息泄露。JWT只是适合在网络中传输一些非敏感的信息
-
Signature
签名哈希部分是对上面两部分数据签名,将base64编码后的header和payload数据用
.
连接起来形成前两段token,而第三段token就是使用base64编码后的header和payload数据通过指定的算法生成哈希,以确保数据不会被篡改。首先,需要指定一个密钥(secret)。该密码仅仅保存在服务器中,并且不能向用户公开。然后,使用header中指定的签名算法(默认情况下为HMAC SHA256)生成签名。
加密与解密
先导入坐标
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
使用HS256进行加解密(对称加密)
@Test
public void generateAndParseTokenByHs256() {
// 过期时间
long time = System.currentTimeMillis() + 60*1000;
String token = Jwts.builder()
//Header
.setHeaderParam("typ","JWT")
.setHeaderParam("alg","HS256")
// Payload装载的信息
.claim("userInfo", "levi")
// 过期的时间
.setExpiration(new Date(time))
// 标识 token 唯一的 ID
.setId(UUID.randomUUID().toString())
// 数字签名
.signWith(SignatureAlgorithm.HS256, "admin")
.compact();
// 通过算法和密钥进行加密
System.out.println(token);
// 通过密钥进行解密
Jws<Claims> claimsJws = Jwts.parser().setSigningKey("admin").parseClaimsJws(token);
Claims body = claimsJws.getBody();
System.out.println("body.get(\"userInfo\") = " + body.get("userInfo"));
}
使用RS256进行加解密(非对称加密)
因为RS256是非对称加密算法,所以需要先生成一对公钥和密钥
public class GenerateKey {
public static void main(String[] args) throws NoSuchAlgorithmException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
// 获取公钥和私钥对象
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
log.info("private key: [{}]", Base64.encode(privateKey.getEncoded()));
log.info("public key: [{}]", Base64.encode(publicKey.getEncoded()));
}
}
将生成的公钥和密钥保存为常量
public static final String PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNA···";
public static final String PRIVATE_KEY = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwgg···";
将生成的私钥转换成PrivateKey
/**
* 获取私钥
* @return PrivateKey
* @throws Exception exception
*/
private PrivateKey getPrivateKey() throws Exception {
PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(
new BASE64Decoder().decodeBuffer(Constant.PRIVATE_KEY)
);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(priPKCS8);
}
开始加密(加密时使用PrivateKey
)
@Test
public void generateTokenByRS256() throws Exception {
String token = Jwts.builder()
.claim("username", "levi")
.setExpiration(new Date(System.currentTimeMillis() + 60 * 1000))
.setId(UUID.randomUUID().toString())
.signWith(SignatureAlgorithm.RS256, getPrivateKey())
.compact();
// 获取加密的 token
System.out.println(token);
}
解密前先将公钥转换成PublicKey
/**
* 根据本地存储的公钥获取到 PublicKey 对象
*
* @return PublicKey
*/
private static PublicKey getPublicKey() throws Exception {
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(
new BASE64Decoder().decodeBuffer(PUBLIC_KEY)
);
return KeyFactory.getInstance("RSA").generatePublic(keySpec);
}
开始解密
@Test
public void parseTokenByRS256() throws Exception {
String token = Jwts.builder()
.claim("username", "levi")
.setExpiration(new Date(System.currentTimeMillis() + 60 * 1000))
.setId(UUID.randomUUID().toString())
.signWith(SignatureAlgorithm.RS256, getPrivateKey())
.compact();
// 获取加密的 token
System.out.println(token);
// 解密 token
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(getPublicKey()).parseClaimsJws(token);
Claims body = claimsJws.getBody();
System.out.println(body.get("username"));
}
鉴权
将用户的权限信息放在JWT的负载中,当请求进入时,拦截请求,获取 token 中的用户信息,并进行相应的权限校验逻辑
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix