传统的session:
-
核心原理 服务端创建唯一 Session ,生成专属
SessionID;登录成功后,把SessionID通过 Cookie 发给浏览器保存。 -
访问流程
- 简单、安全,数据存在服务端,前端无法篡改用户信息
- 有状态:服务端需要保存会话,集群部署需做 session 共享
- 过期、注销:服务端销毁 session 即可强制下线
特点
- 会话数据(用户信息、登录状态):存在服务端(内存 / Redis / 数据库)
- 唯一标识 SessionID:存在客户端 Cookie
存储位置
- 客户端后续每次请求,浏览器自动携带 Cookie 里的
SessionID - 服务端根据
SessionID查找对应的会话数据,识别用户、判断登录状态、权限
弊端:若服务器采用集群存储,用户每次访问的服务器端可能都不同,可能上次访问的session存储在服务器A,这次访问服务器B,数据难以同步
token则是将信息存储在本地浏览器中,每次访问服务器会读取本地浏览器的token。
简单理解:token相当于工牌,门卫看到就放行。session是门卫认识你就放行,但换了门卫就不行了
为了防止用户本地篡改信息,JWT提供了签名
JWT 格式:头部。载荷。签名 Header.Payload.Signature 三部分用小数点拼接
1. Header 头部(头)
- 内容:加密算法、令牌类型
- 示例:
alg: HS256(哈希加密)、typ: JWT - 格式:Base64 编码
2. Payload 载荷(主体)
- 存业务数据 (核心)
- 过期时间、签发时间、用户 ID、角色、权限
- ⚠️可被解码查看,不能放密码、敏感数据
- 格式:Base64 编码
3. Signature 签名(防伪)
- 规则:
头部+载荷 + 密钥加密算出 - 作用:防止篡改只要头部、载荷被改,签名校验失败,令牌直接作废
- 只有后端持有密钥,安全
token的配置:
// 7天过期(单位:秒)
private static Long expire = 604800L;
// 32位秘钥(这里示例用的是40位字符串,HS512算法要求密钥长度足够)
private static String secret = "abcdfghiabcdfghiabcdfghiabcdfghi";
/**
* 生成JWT Token
* @param username 用户名,作为Token的主题(Subject)
* @return 生成的JWT字符串
*/
public static String generateToken(String username) {
// 1. 获取当前时间
Date now = new Date();
// 2. 计算Token过期时间:当前时间 + 1000(毫秒/秒) * expire(秒)
// 也就是当前时间往后推 604800 秒(7天)
Date expiration = new Date(now.getTime() + 1000 * expire);
// 3. 构建JWT并返回
return Jwts.builder()
// 设置Header中的自定义参数:type=JWT
.setHeaderParam("type", "JWT")
// 设置Payload中的sub(主题):这里存的是用户名
.setSubject(username)
// 设置Payload中的iat(签发时间):当前时间
.setIssuedAt(now)
// 设置Payload中的exp(过期时间):前面计算好的expiration
.setExpiration(expiration)
// 使用HS512算法和秘钥对JWT进行签名
.signWith(SignatureAlgorithm.HS512, secret)
// 压缩并生成最终的JWT字符串
.compact();
}
解析token:
// 解析token,获取Token中的Claims(载荷信息)
public static Claims getClaimsByToken(String token) {
return Jwts.parser() // 1. 创建JWT解析器
.setSigningKey(secret) // 2. 设置签名密钥,用于验证Token的签名是否合法
.parseClaimsJws(token) // 3. 解析并验证Token,解析失败会抛出异常(如过期、篡改等)
.getBody(); // 4. 获取解析后的载荷部分(包含用户信息、签发时间、过期时间等)
}
token检验:
public static boolean verifyToken(String token) {
try {
getClaimsByToken(token);
return true;
} catch (ExpiredJwtException e) {
// token过期
System.out.println("token已过期");
} catch (SignatureException e) {
// 签名错误、token被篡改
System.out.println("token签名非法");
} catch (Exception e) {
System.out.println("token解析失败");
}
return false;
}
controller实现类:
package com.example.jwtdemo;
import com.example.jwtdemo.jwt.JwtConfig;
import io.jsonwebtoken.Claims;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
/**
* JWT 测试控制类
* 无前端,直接用 ApiPost 调接口
*/
@RestController
@RequestMapping("/jwt")
public class JwtController {
/**
* 1. 登录接口:传入用户名,返回Token
* 地址:GET http://localhost:8080/jwt/login?username=zhangsan
*/
@GetMapping("/login")
public String login(@RequestParam String username) {
// 模拟登录成功,下发token
String token = JwtConfig.generateToken(username);
return "登录成功,你的Token:" + token;
}
/**
* 2. 解析token接口
* 地址:GET http://localhost:8080/jwt/parse?token=xxx
*/
@GetMapping("/parse")
public String parseToken(@RequestParam String token) {
boolean flag = JwtConfig.verifyToken(token);
if (!flag) {
return "Token非法或已失效";
}
Claims claims = JwtConfig.getClaimsByToken(token);
String username = claims.getSubject();
Date expire = claims.getExpiration();
return "解析成功:\n用户名:" + username + "\n过期时间:" + expire;
}
/**
* 3. 需要携带Token才能访问的受保护接口
* http://localhost:8080/jwt/auth?token=xxx
*/
@GetMapping("/auth")
public String authApi(@RequestParam String token) {
if (!JwtConfig.verifyToken(token)) {
return "权限不足,请先登录!";
}
Claims claims = JwtConfig.getClaimsByToken(token);
return "访问受保护资源成功,当前登录用户:" + claims.getSubject();
}
}
apipost中调用get方法输入路径:http://localhost:8080/jwt/login?username=zhangsan

复制token后输入解析路径中:
