web: jwt令牌构成、创建的基本流程及原理
一、JWT 的构成
1. 概念
json web token(JWT) 本质是一串含义验证信息的字符串,服务器根据JWT和密钥,经过加密算法,验证该字符串是否有效。
2. 构成
JWT由Header、Payload、Signature构成,每个部分都是一个token,如下:

它们的实际含义如下:
Header:json字符串,指定加密算法(供Signature使用)和类型(一般写死为“JWT”)
Payload:json字符串,包含通用信息(如发布者iss、发布时间戳iat、过期时间戳exp)和自定义属性(如uid)
Signature:加密函数,输入“Header”、“Payload”、“密钥”,输出密文

3. 验证方式

STEP 1:客户端登录,传递用户名、密码,服务器端验证资格,成功后生成JWT(Header、Payload、Signature皆由服务器端生成)
STEP 2:服务器端返回JWT给客户端,客户端将JWT存储在本地或浏览器中
STEP 3:客户端请求资源,传递JWT,服务器端获得JWT后,使用Header指定的加密算法,输入“Header"、"Payload"、以及服务器端存储的”密钥“,计算Signature的部分,看看服务器端计算的Signature和客户端传递的Signature是否一致,一致则验证通过。
(图片来自https://www.bilibili.com/video/BV13t5PzDEzh)
二、JWT的创建过程
1. 确定 Token 的基本信息(Claims 设计)
JWT 的本质是一个 带签名的 Claims 集合,生成前需明确:
(1)通用声明(Registered Claims)
常见、推荐使用的字段:
| Claim | 含义 | 是否必须 |
|---|---|---|
iss | 签发者(issuer) | 否 |
sub | 主题(subject,一般是用户 ID / username) | 是(推荐) |
aud | 接收方 | 否 |
iat | 签发时间 | 是(推荐) |
exp | 过期时间 | 是(强烈推荐) |
nbf | 生效时间 | 否 |
jti | JWT 唯一标识 | 否 |
(2)自定义声明(Private Claims)
用于业务识别,例如:
| Claim | 含义 | 是否必须 |
|---|---|---|
uid | 用户id | 否 |
name | 用户名 | 否 |
原则:不放敏感信息(如密码)
2. 选择签名算法(Algorithm)
这是 JWT 安全性的核心。
| 算法类型 | 示例 | 特点 |
|---|---|---|
| 对称加密 | HS256 / HS512 | 简单、性能高,签发与验证用同一密钥 |
| 非对称加密 | RS256 / ES256 | 私钥签发,公钥验签,更安全 |
业务系统中最常见:
- 单体 / 内部系统:
HS256 - 微服务 / 第三方接入:
RS256
3. 准备密钥(Secret / Key)
- HS256:一段足够复杂的字符串(≥ 256 bit)
- RS256:RSA 私钥(签名)+ 公钥(验证)
密钥应存放在:配置中心 如 application.yml环境变量Vault
❌ 不要硬编码在代码中
如配置在yml中
jwt: # 设置jwt签名加密时使用的秘钥 admin-secret-key: itcast # 设置jwt过期时间 admin-ttl: 7200000 # 设置前端传递过来的令牌名称 admin-token-name: token user-secret-key: itheima user-ttl: 7200000 user-token-name: authentication4. 构造 Claims
将步骤 1 中的字段写入 token 负载:
- 标准声明
- 自定义业务字段
- 时间字段统一使用
Date
Map<String, Object> claims = new HashMap<>(); claims.put(JwtClaimsConstant.USER_ID,user.getId()); String token = JwtUtil.createJWT(jwtProperties.getUserSecretKey(), jwtProperties.getUserTtl(), claims);5. 生成并签名 JWT
将 Header + Payload 使用指定算法和密钥进行签名,得到最终的 JWT 字符串:
xxxxx.yyyyy.zzzzz 6. 返回 Token 给客户端
常见返回方式:
JSON 响应体
{ "access_token": "...", "expires_in": 7200 } HTTP Header
Authorization: Bearer <jwt> 三、Spring Boot 中JWT 生成的标准代码结构
代码来自于苍穹外卖,它自己定义了一个JwtUtil类专门处理JWT相关,其中定义了一个createJWT方法,用于创建JWT。
这个例子只是生成JWT的一种,不一定按照这样做
0. 定义createJWT方法,标准化JWT创建流程
苍穹外卖里面既有商家端的员工登录,又有客户端的微信用户登录,都要用到JWT,因此它写了一个通用类来专门处理JWT相关,它的流程是这样的:
1. 传入secretKey(密钥)、ttl(生存时间)、claims(一个HashMap,存储payload部分的信息)
2. 指定加密算法
3. 根据ttl(生存时间) 计算exp(过期时间)
4. 构建JWT,调用官方的Jwts传入 claims、签名(指定的算法、密钥等)、过期时间
package com.sky.utils; import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import java.nio.charset.StandardCharsets; import java.util.Date; import java.util.Map; public class JwtUtil { /** * 生成jwt * 使用Hs256算法, 私匙使用固定秘钥 * * @param secretKey jwt秘钥 * @param ttlMillis jwt过期时间(毫秒) * @param claims 设置的信息 * @return */ public static String createJWT(String secretKey, long ttlMillis, Map<String, Object> claims) { // 指定签名的时候使用的签名算法,也就是header那部分 SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; // 生成JWT的时间 long expMillis = System.currentTimeMillis() + ttlMillis; Date exp = new Date(expMillis); // 设置jwt的body JwtBuilder builder = Jwts.builder() // 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的 .setClaims(claims) // 设置签名使用的签名算法和签名使用的秘钥 .signWith(signatureAlgorithm, secretKey.getBytes(StandardCharsets.UTF_8)) // 设置过期时间 .setExpiration(exp); return builder.compact(); } /** * Token解密 * * @param secretKey jwt秘钥 此秘钥一定要保留好在服务端, 不能暴露出去, 否则sign就可以被伪造, 如果对接多个客户端建议改造成多个 * @param token 加密后的token * @return */ public static Claims parseJWT(String secretKey, String token) { // 得到DefaultJwtParser Claims claims = Jwts.parser() // 设置签名的秘钥 .setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8)) // 设置需要解析的jwt .parseClaimsJws(token).getBody(); return claims; } } 1. 在application.yml中配置jwt
上面说到secret key 最好放在配置中,不要放在代码里,因此苍穹外卖在yml中配置
sky: jwt: # 设置jwt签名加密时使用的秘钥 admin-secret-key: itcast # 设置jwt过期时间 admin-ttl: 7200000 # 设置前端传递过来的令牌名称 admin-token-name: token user-secret-key: itheima user-ttl: 7200000 user-token-name: authentication2. 配置JwtProperties,读取application.yml中jwt的相关配置
上面的yml配置的jwt属性,需要通过properties读取到
package com.sky.properties; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Component @ConfigurationProperties(prefix = "sky.jwt") @Data public class JwtProperties { /** * 管理端员工生成jwt令牌相关配置 */ private String adminSecretKey; private long adminTtl; private String adminTokenName; /** * 用户端微信用户生成jwt令牌相关配置 */ private String userSecretKey; private long userTtl; private String userTokenName; } 3. 在登录的Controller中生成JWT,返回给用户
基本就是两个步骤
1. 创建claims,是一个hashMap,存入“用户id:xxxx" 的键值对。这个东西用来唯一标识是哪个用户。
2. 创建JWT,传入secretKey(密钥)、ttl(生存时间)、claims。createJWT方法内部再进一步的设置加密算法、
员工登录
package com.sky.controller.admin; import com.sky.constant.JwtClaimsConstant; import com.sky.dto.EmployeeDTO; import com.sky.dto.EmployeeLoginDTO; import com.sky.dto.EmployeePageQueryDTO; import com.sky.entity.Employee; import com.sky.properties.JwtProperties; import com.sky.result.PageResult; import com.sky.result.Result; import com.sky.service.EmployeeService; import com.sky.utils.JwtUtil; import com.sky.vo.EmployeeLoginVO; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.HashMap; import java.util.Map; /** * 员工管理 */ @RestController @RequestMapping("/admin/employee") @Slf4j @Api(tags = "员工相关接口") public class EmployeeController { @Autowired private EmployeeService employeeService; @Autowired private JwtProperties jwtProperties; /** * 登录 * @param employeeLoginDTO * @return */ @PostMapping("/login") @ApiOperation(value = "员工登录") public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO) { log.info("员工登录:{}", employeeLoginDTO); Employee employee = employeeService.login(employeeLoginDTO); //登录成功后,生成jwt令牌 Map<String, Object> claims = new HashMap<>(); claims.put(JwtClaimsConstant.EMP_ID, employee.getId()); String token = JwtUtil.createJWT( jwtProperties.getAdminSecretKey(), jwtProperties.getAdminTtl(), claims); EmployeeLoginVO employeeLoginVO = EmployeeLoginVO.builder() .id(employee.getId()) .userName(employee.getUsername()) .name(employee.getName()) .token(token) .build(); return Result.success(employeeLoginVO); }微信用户登录
package com.sky.controller.user; import com.sky.constant.JwtClaimsConstant; import com.sky.dto.UserLoginDTO; import com.sky.entity.User; import com.sky.properties.JwtProperties; import com.sky.result.Result; import com.sky.service.UserService; import com.sky.utils.JwtUtil; import com.sky.vo.UserLoginVO; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap; import java.util.Map; @RestController @RequestMapping("/user/user") @Api(tags = "C端用户相关接口") @Slf4j public class UserController { @Autowired private UserService userService; @Autowired private JwtProperties jwtProperties; /** * 微信登录 * @param userLoginDTO * @return */ @PostMapping("/login") @ApiOperation("微信登录") public Result<UserLoginVO> login(@RequestBody UserLoginDTO userLoginDTO){ log.info("微信用户登录:{}",userLoginDTO.getCode()); //微信登录 User user = userService.wxLogin(userLoginDTO); //为微信用户生成jwt令牌 Map<String, Object> claims = new HashMap<>(); claims.put(JwtClaimsConstant.USER_ID,user.getId()); String token = JwtUtil.createJWT(jwtProperties.getUserSecretKey(), jwtProperties.getUserTtl(), claims); UserLoginVO userLoginVO = UserLoginVO.builder() .id(user.getId()) .openid(user.getOpenid()) .token(token) .build(); return Result.success(userLoginVO); } } 四、JWT 与 Spring Security 的关系
- JWT 本身只负责“凭证”
- Spring Security 负责:
- 校验 JWT
- 解析 Claims
- 构造
Authentication
JWT ≠ 登录框架,而是 无状态认证载体