
| JWT | 传统Session | |
|---|---|---|
| 存储位置 | 客户端 | 服务器 |
| 存储数据 | Token | Session ID + 服务器端存储的会话数据 |
| 存储方式 | 无状态 | 有状态 |
| 跨域支持 | 支持 | 需要额外配置 |
| 可扩展性 | 高 | 低 |
| 安全性 | 高 | 中 |
| 网络开销 | 低 | 高 |
| 扩展性 | 高 | 低 |
| 动态更改权限 | 需要重新签发Token | 服务器端配置即可 |
| 服务器状态管理 | 无需管理 | 需要管理和维护 |
JWT的结构由三部分组成,分别是标头、有效负载、签名算法,中间使用 点 进行隔开。
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxMmY0MzMyYy01MmRhLTQ0MDktODJjZS1hODBkZjNmMDIwYjMiLCJzdWIiOiJhbGwiLCJpYXQiOjE3MTcxMTc3MTMsImV4cCI6MTcxNzExOTUxMywidXNlcm5hbWUiOiJ6aGFuZ3NhbiIsInVzZXJJZCI6IjEwMDEifQ.wfrMHoLZubZksALfad5BAG7oNUXbMwrXxHhgRTAtOtI
| Payload的内置字段 | 说明 |
|---|---|
| iss(Issuer) | 令牌的签发者 |
| sub(Subject) | 所面向的用户或实体 |
| aud(Audience) | 令牌的接收者 |
| exp(Expiration Time) | 令牌的过期时间(以UNIX时间戳表示) |
| nbf(Not Before) | 令牌的生效时间(以UNIX时间戳表示) |
| iat(Issued At) | 令牌的签发时间(以UNIX时间戳表示) |
| jti(JWT ID) | 令牌的唯一标识符 |
原文链接:https://blog.csdn.net/qq_46921028/article/details/131298671
var encodestr = base64urlEncode(header) + "." + base64urlEncode(paylod);
var signature = HMACSHA256(encodestr,"secret");

<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.3.12.RELEASEversion>
<relativePath/>
parent>
...
<properties>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>io.jsonwebtokengroupId>
<artifactId>jjwtartifactId>
<version>0.9.1version>
dependency>
dependencies>
如果jdk大于1.8,还需要引入以下依赖
<dependency>
<groupId>javax.xml.bindgroupId>
<artifactId>jaxb-apiartifactId>
<version>2.3.0version>
dependency>
<dependency>
<groupId>com.sun.xml.bindgroupId>
<artifactId>jaxb-implartifactId>
<version>2.3.0version>
dependency>
<dependency>
<groupId>org.glassfish.jaxbgroupId>
<artifactId>jaxb-coreartifactId>
<version>2.3.0version>
dependency>
<dependency>
<groupId>javax.activationgroupId>
<artifactId>activationartifactId>
<version>1.1.1version>
dependency>
@SpringBootTest
class JwtApplicationTests {
/** AES 算法 */
private static final String ALGORITHM_AES="AES";
@Test
public void testCreatJwt() throws NoSuchAlgorithmException {
//定义秘钥,可以自己定义,随便一个字符串都可以,专业一些的话就用密钥生成工具吧
String secretKey = getKey();
System.out.println("生成的密钥是:" + secretKey);
// 使用Jwts工具类构建一个令牌
String token = Jwts.builder()
// 1.设置JWT头部信息(类型和加密算法)
.setHeaderParam("typ", "JWT")
.setHeaderParam("alg", "HS256")
// 2.设置JWT载荷数据
.setId(UUID.randomUUID().toString()) //内置字段jti:表示唯一ID
.setSubject("all") //内置字段sub:面向所有用户
.setIssuedAt(new Date()) //内置字段ita:token创建时间
.setExpiration(new Date(System.currentTimeMillis() + 30 * 60 * 1000)) //内置字段exp:token过期时间,30分钟
.claim("username", "zhangsan") //自定义字段,kv格式
.claim("userId", "1001") //自定义字段
// 3.设置JWT签名信息(加密算法,秘钥)
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact(); //最后调用compact()方法生成最终的token
//由于使用UUID生成唯一标识,所以每次生成的token都不一样
System.out.println("token = " + token);
}
/**
* 生成密钥
* @return
* @throws NoSuchAlgorithmException
*/
private String getKey() throws NoSuchAlgorithmException {
/**
* 创建KeyGenerator实例
* algorithm密钥算法
* AES
* DES
* DESede
* HmacSHA1
* HmacSHA224
* HmacSHA256
* HmacSHA384
* HmacSHA512
* RC2
*/
KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM_AES);
//指定生成密钥的大小;AES密钥长度只能=128、192、256
keyGenerator.init(256);
//指定生成密钥随机源:keyGenerator.init(SecureRandom secureRandom)
//指定生成密钥大小、随机源:keyGenerator.init(int size, SecureRandom secureRandom)
/**
* 借助Base64转换生成的密钥
* 通常加密后要把密钥保存下来,解密时使用密钥重建SecertKey,生成的密钥是字节数组不利于保存,所以借助Base64转换成字符串
*/
return Base64.getEncoder().encodeToString(keyGenerator.generateKey().getEncoded());
}
}
生成的密钥是:pI9g7kkh0IgqHC27U7FYgAQtquy9PGPINCUvko2Qyyo=
token = eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxMmY0MzMyYy01MmRhLTQ0MDktODJjZS1hODBkZjNmMDIwYjMiLCJzdWIiOiJhbGwiLCJpYXQiOjE3MTcxMTc3MTMsImV4cCI6MTcxNzExOTUxMywidXNlcm5hbWUiOiJ6aGFuZ3NhbiIsInVzZXJJZCI6IjEwMDEifQ.wfrMHoLZubZksALfad5BAG7oNUXbMwrXxHhgRTAtOtI
地址:https://tooltt.com/jwt-decode/

刚才的token过期了,重新生成了一下
@Test
public void testcheckToken() {
// 秘钥,刚才生成的密钥
String secretKey = "Y28Ijg521FgN31ZgpD1hZpOYd8fTMrZwNcMgds+D91I=";
// 待验证的token
String tokenStr = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4M2M3NTVkNC1jNzJlLTRlZjctYjY1MC1jYjdlZWRkYWNjYWIiLCJzdWIiOiJhbGwiLCJpYXQiOjE3MTcxMjQxNjQsImV4cCI6MTcxNzEyNTk2NCwidXNlcm5hbWUiOiJ6aGFuZ3NhbiIsInVzZXJJZCI6IjEwMDEifQ.gM89JWUOAQu8KpYgXbom9KGXB1ZcqSUqzj5eW8cg_HU";
// 通过密钥验证签名是否被篡改
JwtParser jwtParser = Jwts.parser();
Jws<Claims> claimsJws = jwtParser
.setSigningKey(secretKey)
.parseClaimsJws(tokenStr);
// 获取头
JwsHeader header = claimsJws.getHeader();
// 获取载荷
Claims body = claimsJws.getBody();
// 获取签名
String signature = claimsJws.getSignature();
System.out.println("头信息:" + header);
System.out.println("载荷信息:" + body);
System.out.println("签名信息:" + signature);
}
头信息:{typ=JWT, alg=HS256}
载荷信息:{jti=83c755d4-c72e-4ef7-b650-cb7eeddaccab, sub=all, iat=1717124164, exp=1717125964, username=zhangsan, userId=1001}
签名信息:gM89JWUOAQu8KpYgXbom9KGXB1ZcqSUqzj5eW8cg_HU
| 异常 | 原因 |
|---|---|
| SignatureVerificationException | 签名不一致异常 |
| TokenExpiredException | 令牌过期异常 |
| AlgorithmMismatchException | 算法不匹配异常 |
| InvalidClaimException: | 失效的payload异常 |
用这个吧,反正后面要写OAuth2
<dependency>
<groupId>com.auth0groupId>
<artifactId>java-jwtartifactId>
<version>3.4.0version>
dependency>
# JWT 配置
jwt:
secret: Y28Ijg521FgN31ZgpD1hZpOYd8fTMrZwNcMgds+D91I= # 加密密钥
expire: 1800 # token有效时长 S
server:
port: 9999
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
# ...... 其他配置
package com.kgc.utils;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Calendar;
import java.util.Map;
/**
* @author: zjl
* @datetime: 2024/5/31
* @desc: 复兴Java,我辈义不容辞
*/
@Component
public class JWTUtils {
@Value("${jwt.secret}")
public String secret;
@Value("${jwt.expire}")
public Integer tokenExpire;
/**
* 生成 JWT 令牌
* @param map 传入的 Payload 数据
* @return 返回生成的令牌
*/
public String getToken(Map<String,String> map){
JWTCreator.Builder builder = JWT.create();
// 遍历传入的 Payload 数据,并添加到 Builder 中
map.forEach((k,v)->{
builder.withClaim(k,v);
});
Calendar instance = Calendar.getInstance();
instance.add(Calendar.SECOND,tokenExpire);
// 设置过期时间为 XX 秒后
builder.withExpiresAt(instance.getTime());
// 使用 HMAC256 签名算法进行签名,并返回令牌字符串
return builder.sign(Algorithm.HMAC256(secret)).toString();
}
/**
* 获取令牌中的 Payload 数据
* @param token 要解析的令牌字符串
* @return 解码后的令牌对象(DecodedJWT)
*/
public DecodedJWT verify(String token){
// 创建一个 JWTVerifier 实例,使用相同的密钥构建,并对令牌进行验证和解码
return JWT.require(Algorithm.HMAC256(secret)).build().verify(token);
}
}
实体类
@Data
@NoArgsConstructor
@ToString
@AllArgsConstructor
public class User {
private int id;
private String userCode;
private String userName;
private String userPassword;
private String phone;
}
mapper
public interface UserMapper {
@Select("SELECT * FROM SMBMS_USER WHERE USERCODE=#{userCode}")
User selectUserByUserCode(String userCode);
}
service
@Service
@Slf4j
public class UserService {
@Resource
private UserMapper userMapper;
public User login(String userCode,String userPassword){
User user = userMapper.selectUserByUserCode(userCode);
if(user!=null && userPassword.equals(user.getUserPassword())){
return user;
}
return null;
}
}
统一返回模板
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {
private int code;
private String message;
private T data;
public Result(T data) {
this.code = 200;
this.message = "success";
this.data = data;
}
public Result(T data, boolean success, String message) {
if (success) {
this.code = 200;
this.message = "success";
} else {
this.code = 500; // 自定义错误状态码(示例为500)
this.message = message;
}
this.data = data;
}
public Result(int code, String message) {
this.code = code;
this.message = message;
this.data = null;
}
/**
* 返回执行失败的结果(默认状态码为500)
*
* @param message 提示信息
* @return 失败的结果对象
*/
public static <T> Result<T> fail(String message) {
return new Result<>(500, message);
}
/**
* 返回执行失败的结果(自定义状态码和提示信息)
*
* @param code 状态码
* @param message 提示信息
* @return 失败的结果对象
*/
public static <T> Result<T> fail(int code, String message) {
return new Result<>(code, message);
}
}
package com.kgc.interceptor;
/**
* @author: zjl
* @datetime: 2024/5/31
* @desc: 复兴Java,我辈义不容辞
*/
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.kgc.utils.JWTUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
/**
* JWTInterceptor是一个拦截器,用于验证请求头中的JWT令牌是否有效。
* 当有请求进入时,该拦截器会首先从请求头中获取令牌,并尝试验证其有效性。
* 如果令牌验证成功,则放行请求;否则,拦截请求并返回相应的错误信息。
*/
@Component
public class JWTInterceptor implements HandlerInterceptor {
@Resource
private JWTUtils jwtUtils;
@Resource
private ObjectMapper objectMapper;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 创建一个Map对象,用于存储响应信息
Map<String, Object> map = new HashMap<>();
// 从请求头中获取令牌
String token = request.getHeader("token");
try {
jwtUtils.verify(token); // 验证令牌的有效性
return true; // 放行请求
} catch (SignatureVerificationException e) {
e.printStackTrace();
map.put("msg", "无效签名!");
} catch (TokenExpiredException e) {
e.printStackTrace();
map.put("msg", "token过期!");
} catch (AlgorithmMismatchException e) {
e.printStackTrace();
map.put("msg", "token算法不一致!");
} catch (Exception e) {
e.printStackTrace();
map.put("msg", "token无效!!");
}
map.put("state", false); // 设置状态为false
// 将Map转化为JSON字符串(使用Jackson库)
String json = objectMapper.writeValueAsString(map);
response.setContentType("application/json;charset=UTF-8"); // 设置响应的Content-Type
response.getWriter().println(json); // 将JSON字符串写入响应中
return false; // 不放行请求
}
}
/**
* InterceptorConfig 是一个配置类,用于添加拦截器。
* 在这个类中,我们可以配置需要拦截的接口路径以及排除不需要拦截的接口路径。
* 在这个例子中,我们添加了JWTInterceptor拦截器来对请求进行token验证,
* 并设置了"/user/test"接口需要进行验证,而"/user/login"接口则被排除在验证之外,即所有用户都放行登录接口。
*/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Resource
private JWTInterceptor jwtInterceptor;
/**
* 添加拦截器配置
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtInterceptor)
.addPathPatterns("/user/test") // 对"/user/test"接口进行token验证
.excludePathPatterns("/user/login"); // 所有用户都放行登录接口
}
}
package com.kgc.controller;
import com.auth0.jwt.exceptions.*;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.kgc.pojo.User;
import com.kgc.service.UserService;
import com.kgc.utils.JWTUtils;
import com.kgc.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
/**
* @author: zjl
* @datetime: 2024/5/31
* @desc: 复兴Java,我辈义不容辞
*/
@RestController
@Slf4j
@RequestMapping("/user")
public class UserController {
@Resource
private UserService userService;
@Resource
private JWTUtils jwtUtils;
@RequestMapping("/login")
public Result<Map<String, Object>> login(User user) {
// 打印用户名和密码
log.info("用户名: [{}]", user.getUserCode());
log.info("密码: [{}]", user.getUserPassword());
// 创建结果对象
Result<Map<String, Object>> result;
try {
// 调用userService的login方法进行用户认证
User loginUser = userService.login(user.getUserCode(),user.getUserPassword());
if(loginUser == null){
return new Result<>(0, "认证失败");
}
// 获取用户ID和用户名,并将其放入payload
Map<String, String> payload = new HashMap<>();
payload.put("id", String.valueOf(loginUser.getId()));
payload.put("name", loginUser.getUserName());
// 生成JWT的令牌
String token = jwtUtils.getToken(payload);
// 构造成功的结果对象
result = new Result<>(200, "认证成功");
result.setData(new HashMap<>());
result.getData().put("token", token); // 响应token
} catch (Exception e) {
// 构造失败的结果对象
result = Result.fail(500, e.getMessage());
}
return result;
}
@RequestMapping("/test")
public Result<Map<String, Object>> test(HttpServletRequest request) {
// 创建结果对象
Result<Map<String, Object>> result;
try {
Map<String, Object> map = new HashMap<>();
// 处理自己的业务逻辑
// 从请求头中获取token
String token = request.getHeader("token");
if(StringUtils.isEmpty(token)){
return new Result<>(0, "请先登录!");
}
// 校验并解析token
DecodedJWT verify = jwtUtils.verify(token);
// 打印解析出的用户id和用户名
log.info("用户id: [{}]", verify.getClaim("id").asString());
log.info("用户name: [{}]", verify.getClaim("name").asString());
// 构造成功的结果对象
result = new Result<>(200, "请求成功!");
result.setData(map);
} catch (Exception e) {
// 构造失败的结果对象
result = Result.fail(500, e.getMessage());
}
return result;
}
@RequestMapping("/other")
public Map<String, Object> test(String token) {
Map<String, Object> map = new HashMap<>();
try {
jwtUtils.verify(token);
map.put("msg", "验证通过~~~");
map.put("state", true);
} catch (TokenExpiredException e) {
map.put("state", false);
map.put("msg", "Token已经过期!!!");
} catch (SignatureVerificationException e){
map.put("state", false);
map.put("msg", "签名错误!!!");
} catch (AlgorithmMismatchException e){
map.put("state", false);
map.put("msg", "加密算法不匹配!!!");
} catch (Exception e) {
e.printStackTrace();
map.put("state", false);
map.put("msg", "无效token~~");
}
return map;
}
}