JWT概念及JAVA使用
前言
- JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案
认证方式
认证方式一般有两种
session
认证JWT
认证
一、session认证
1、步骤
过去,我们都是使用
session
认证,步骤如下
- 前端访问登录接口
- 登录接口验证完账号密码没问题时,在
redis
中插入一条数据session_id:用户信息,表示这个
session_id登录了,然后将这个
session_id`返回给前端- 前端接收到
session_id
后,放入Cookie
中,在后续请求接口中自动带上。- 后续接口在收到
Cookie
时,拿着session_id
去查询Redis
,获得用户登录身份
- 未查询到:返回前端一个状态码,前端跳转到登录页
- 查到了:执行正常逻辑,并且返回给前端数据
- 登出时:可以让后端将这个
session_id
在redis
中删了即可
2、优劣势
- 优势:无
- 劣势:
- 如果用户数据量大,那么内存压力也会相应变大
- 如果多个微服务,那么需要远程调用登录服务校验,或者每个服务都需要连接这个
Redis
,并且重复编写请求拦截校验代码
二、JWT认证
1、步骤
- 用户访问登录接口
- 登录成功后,后端根据
JWT
提供的方法,设置Header(头部),payload(负载),Signature(签名)
- 将生成的结果返回给前端,前端保存到
sessionStorage/localStorage
中,每次请求带上这个值,放入header
中- 后端接受到后,直接使用
JWT
提供的方法,校验根据.
签名两个值生成的签名
与生成签名时的密钥
是否一致
2、生成签名公式
Header:有令牌的类型和所使用的签名算法,如HMAC、SHA256、RSA;使用Base64编码组成;(Base64是一种编码,不是一种加密过程,可以被翻译成原来的样子)
Base64URL({ "alg":"HS256", "typ":"JWT" })
Payload:有效负载,包含声明;声明是有关实体(通常是用户)和其他数据的声明,不放用户敏感的信息,如密码。同样使用Base64编码
Base64URL({ "id":1, "name":"张三", "username":"zhangSan" })
Signature:前面两部分都使用Base64进行编码,前端可以解开知道里面的信息。Signature需要使用编码后的header和payload
加上我们提供的一个密钥,使用header中指定的签名算法(HS256)进行签名。签名的作用是保证JWT没有被篡改过HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret(密钥,只有服务器知道))
签名:签名的过程实际上是对头部以及负载内容进行签名,防止内容被窜改。如果有人对头部以及负载的内容解码之后进行修改,再进行编码,最后加上之前的签名组合形成新的JWT的话,那么服务器端会判断出新的头部和负载形成的签名和JWT附带上的签名是不一样的。如果要对新的头部和负载进行签名,在不知道服务器加密时用的密钥的话,得出来的签名也是不一样的
3、代码实践
- 我这里将
JWT
所有操作生成Token,校验Token,获取Payload中信息
统一提取到JwtTokenUtil
类中
package com.tcc.utils;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.InvalidClaimException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.sun.org.apache.xml.internal.security.algorithms.SignatureAlgorithm;
import java.util.Date;
public class JwtTokenUtil {
// 密钥
private static final String SECRET = "123456";
// 一天毫秒数
private static final long EXPIRE_TIME = 24 * 60 * 60 * 1000;
/**
* 获取Token
* @Author: tcc
* @Date: 2025/3/1 16:17
*/
public static String getToken() {
return JWT.create()
.withClaim("id", "1") // Payload 用户信息
.withClaim("name", "张三") // Payload 用户信息
.withClaim("username", "zhangSan")
.withExpiresAt(new Date(System.currentTimeMillis() + EXPIRE_TIME)) // 过期时间,一天
.sign(Algorithm.HMAC256(SECRET));
}
/**
* 校验Token
* @Author: tcc
* @Date: 2025/3/1 16:21
*/
public static void validateToken(String token) {
try {
JWT.require(Algorithm.HMAC256(SECRET)).build().verify(token);
System.out.println("验证成功");
} catch (Exception e) {
e.printStackTrace();
System.out.println("验证失败");
}
// ExpiredJwtException:当 JWT 的过期时间(exp 声明)已过,验证时就会抛出此异常
// SignatureException:当 JWT 的签名验证失败时,会抛出该异常。这可能是因为 token 被篡改,或者使用了错误的密钥进行验证。
// MalformedJwtException 当 JWT 的格式不正确时,例如 token 不是一个有效的 JWT 字符串,或者不满足 JWT 的结构(由头部、载荷和签名三部分组成,用 . 分隔),就会抛出此异常。
// UnsupportedJwtException:当 JWT 使用了不支持的签名算法,或者 token 包含不支持的类型、声明等时,会抛出此异常
// IllegalArgumentException: 当 token 为 null 或者为空字符串时,就会抛出此异常
}
/**
* 获取 payload 信息
* @Author: tcc
* @Date: 2025/3/1 16:23
*/
public static void getPayload(String token) {
DecodedJWT verify = JWT.require(Algorithm.HMAC256(SECRET)).build().verify(token);
System.out.println(verify.getExpiresAt()); // 获取 过期时间
System.out.println();
verify.getClaims().forEach((key, value) -> {
System.out.println(key + ":" + value.asString());
});
}
}
测试代码
package com.tcc.controller;
import com.tcc.utils.JwtTokenUtil;
import org.junit.jupiter.api.Test;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
@Test
void login() {
// ------- 验证账号密码 省略 -------
// 通过 每次都会随机生成,但是每个都可以校验成功
System.out.println(JwtTokenUtil.getToken());
}
@Test
void validateToken() {
// 根据上面token复制而来
String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoi5byg5LiJIiwiaWQiOiIxIiwiZXhwIjoxNzQwOTA0NjgyLCJ1c2VybmFtZSI6InpoYW5nU2FuIn0.8XickAlHQI14v9SE-z_GDDpr5Oed8fVcuw4UAetccdY";
JwtTokenUtil.validateToken(token);
}
@Test
void getPayload() {
String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoi5byg5LiJIiwiaWQiOiIxIiwiZXhwIjoxNzQwOTA0NjgyLCJ1c2VybmFtZSI6InpoYW5nU2FuIn0.8XickAlHQI14v9SE-z_GDDpr5Oed8fVcuw4UAetccdY";
JwtTokenUtil.getPayload(token);
}
}
效果
三、总结
JWT
默认是不加密的payload
里面内容也是根据Base64URL
编码转化了一下,并没有加密,所以不能存放密码等信息,当然也可以在生成原始Token
的时候再加密一次JWT
最大的缺点是:由于服务器不保存session
状态,当前端登出操作时,无法废除Token
,也就是说,一旦Token
签发了,再到期之前就会始终有效
- 解决办法:
- 登出时,前端删除
Token
信息即可- 登陆成功后返回前端时间较短的
accessToken
,以及一个时间较长的refreshToken(用来重新获取token)
,前端清除refreshToken
后则无法获取新token
JWT
本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。