JWT原理--与语言无关
普通JWT整体结构
JWT 字符串长这样,三段用 . 分隔:
header.payload.signature
每一段都是 Base64URL 编码(不是加密!只是编码,可逆,随便解码看内容)
Base64URL = Base64 的安全变体:把
+→-,/→_,去掉末尾=,适合放在 URL、请求头里。解码不需要密钥,任何人都能解。
第1段:Header(JSON 对象)
这是第一个 JSON,描述「用什么算法做签名」
示例 JSON:
{
"alg": "HS256",
"typ": "JWT"
}
alg:签名算法typ:固定写 JWT,标识类型
👉 变换过程:
JSON → UTF-8字节 → Base64URL 编码 → 第一段字符串
第2段:Payload(载荷,业务 JSON 对象,重点!)
业务数据,也是大家最关心的 JSON。
分为两类字段:标准声明(保留字段) + 自定义字段
示例 JSON:
{
"sub": "user_10086",
"name": "张三",
"exp": 1760000000,
"iat": 1759990000,
"role": "admin"
}
标准常用声明(可选,不是必须写):
sub:主题,一般用户IDiat:issued at,签发时间(时间戳)exp:expiration,过期时间(重点!服务器校验这个)iss:issuer,签发者aud:audience,接收方
✅ 重点:Payload 是明文!Base64URL 只是编码,前端拿到 JWT 可以直接解码看到里面所有内容
❌ 不要放密码、银行卡、隐私敏感数据!
👉 变换过程:
Payload JSON → UTF-8字节 → Base64URL 编码 → 第二段字符串
第3段:Signature(签名,即我们以前讲的验证字符串)
签名计算规则:
签名 = 签名算法(Base64URL(header) + "." + Base64URL(payload), 密钥)
拿 HS256(HMAC-SHA256)举例:
- 拼接串:
base64Header.base64Payload - 使用密钥,对这个拼接字符串做 HMAC-SHA256 哈希
- 把哈希结果字节做 Base64URL 编码 → 第三段字符串
签发完整流程(生成JWT)
输入:Header JSON + Payload JSON + 密钥
- Header JSON → base64url → s1
- Payload JSON → base64url → s2
- 拼接
s1.s2,用密钥+指定算法算出签名 → s3 - 最终 JWT =
s1.s2.s3
全程:s1、s2 任何人都能解码还原回原始 JSON;只有 s3 依赖密钥。
JWT在Spring-boot-web中的使用--cookie版
普通字符串验证版本,可以自己扩展。
借助本博客《Spring_boot_web中的Session与拦截器鉴权》里的前端,修改后端内容。
pom.xml中添加依赖:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.6</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.6</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.6</version>
<scope>runtime</scope>
</dependency>
添加jwt的工具类JwtUtil.java:
package com.example.demo_web.Util;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.Date;
@Component
public class JwtUtil
{
// HS256 要求密钥长度 ≥ 32 字节(256位)
private final SecretKey secretKey = Keys
.hmacShaKeyFor("abcdefghijklmnopqrstuvwxyz123456".getBytes(StandardCharsets.UTF_8));
private final long expireMs = 3600 * 1000L;
// 签发 JWT
public String generate(String userId, int level)
{
long now = System.currentTimeMillis();
return Jwts.builder().subject(userId) // 用户标识
.claim("level", level) // 自定义业务字段,权限等级
.issuedAt(new Date(now)) // 签发时间
.expiration(new Date(now + expireMs)) // 过期时间
// 上面按需要写,下面两行是签名和拼接成jwt字符串
.signWith(secretKey, Jwts.SIG.HS256) // 显式指定签名算法
.compact();
}
// 验签 + 解析,直接返回载荷
// 对于返回的Claims:
// claims.getSubject(); // 拿 sub
// claims.get("level", String.class); // 拿自定义字段
// claims.getExpiration(); // 拿过期时间
//验签失败,抛出异常
public Claims parse(String token)
{
return Jwts.parser()// 创建空解析器
.verifyWith(secretKey)// 设置密码
.build()// 生成解析器
.parseSignedClaims(token)// 拆三段 + 验签 + 解码
.getPayload();// 获取负载
}
}
修改控制器My_Controller.java
package com.example.demo_web.Controllers;
import java.util.Map;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.example.demo_web.Util.JwtUtil;
import io.jsonwebtoken.Claims;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@RestController
public class My_Controller
{
private static final String TOKEN_COOKIE = "access_token";
private final JwtUtil jwtUtil;
public My_Controller(JwtUtil jwtUtil)
{
this.jwtUtil = jwtUtil;
}
@PostMapping("/login")
public String log_In(@RequestBody Map<String, String> body, HttpServletResponse response)
{
String username = body.get("username");
if (username.length() > 4)
{
String jwt = jwtUtil.generate(username, 8);
Cookie cookie = new Cookie(TOKEN_COOKIE, jwt);
cookie.setHttpOnly(true); // 核心:JS 无法读取,防XSS
cookie.setSecure(false); // 本地HTTP,生产HTTPS设true
cookie.setPath("/"); //访问服务器任意路径,都携带此cookie
cookie.setMaxAge(3600); // Cookie有效期,与JWT过期时间对齐
cookie.setAttribute("SameSite", "Lax"); // 基础防CSRF
response.addCookie(cookie);
return "OK";
}
else
{
//以下代码也可以用于登出(清除指定cookie)
Cookie cookie = new Cookie(TOKEN_COOKIE, "");
cookie.setHttpOnly(true);
cookie.setSecure(false);
cookie.setPath("/");
cookie.setMaxAge(0);
response.addCookie(cookie);
return "No";
}
}
@GetMapping("getstring")
public String getSth(HttpServletRequest request)
{
String token = null;
if (request.getCookies() != null)
{
for (Cookie cookie : request.getCookies())
{
if ("access_token".equals(cookie.getName()))
{
token = cookie.getValue();
break;
}
}
}
if (token == null)
{
return "未登录";
}
// 验签 + 解析
try
{
Claims claims = jwtUtil.parse(token);
return "用户:" + claims.getSubject() + "权限:" + claims.get("level", Integer.class) + "张三家的大黑狗昨天下河游泳了";
} catch (Exception e)
{
// 签名错误、过期、格式非法都会抛出异常
return "Token无效或已过期";
}
}
}
至此,使用cookie做载体的jwt可以正常工作。
在浏览器开发者工具(F12)的应用->cookie里可以看到名为"access_token"的cookie值,形如eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhbGljZSIsImxldmVsIjo4LCJpYXQiOjE3OTI4NTY0NDgsImV4cCI6MTc5Mjg2MDA0OH0.FqKvx68uUzU-D23Zj9-vAd7eZC5nMOxz6DYNXRo5Zok,HttpOnly是勾上的。
鸣谢AI豆老师、元老师和智老师。
浙公网安备 33010602011771号