一、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皆由服务器端生成,并且保存生成JWT所使用的密钥)
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中
XML
jwt:
# 设置jwt签名加密时使用的秘钥
admin-secret-key: itcast
# 设置jwt过期时间
admin-ttl: 7200000
# 设置前端传递过来的令牌名称
admin-token-name: token
user-secret-key: itheima
user-ttl: 7200000
user-token-name: authentication
4. 构造 Claims
将步骤 1 中的字段写入 token 负载:
-
标准声明
-
自定义业务字段
-
时间字段统一使用
Date
java
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 给客户端
常见返回方式:
-
HTTP Header
Authorization: Bearer <jwt> -
JSON 响应体
{ "access_token": "...", "expires_in": 7200 }
三、Spring Boot 中JWT 生成的标准代码结构
代码来自于苍穹外卖,它自己定义了一个JwtUtil类专门处理JWT相关,其中定义了一个createJWT方法,用于创建JWT。
这个例子只是生成JWT的一种,不一定按照这样做
0. 定义createJWT方法,标准化JWT创建流程
苍穹外卖里面既有商家端的员工登录,又有客户端的微信用户登录,都要用到JWT,因此它写了一个通用类来专门处理JWT相关,它的流程是这样的:
-
传入secretKey(密钥)、ttl(生存时间)、claims(一个HashMap,表示用户信息)
-
指定加密算法
-
根据ttl(生存时间) 计算exp(过期时间)
-
构建JWT,官方的Jwts要求传入 claims、签名(指定的算法、密钥等)、过期时间
java
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中配置
XML
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: authentication
2. 配置JwtProperties,读取application.yml中jwt的相关配置
上面的yml配置的jwt属性,需要通过properties读取到
java
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,返回给用户
基本就是两个步骤
-
创建claims,是一个hashMap,存入"用户id:xxxx" 的键值对。这个东西用来唯一标识是哪个用户。
-
创建JWT,传入secretKey(密钥)、ttl(生存时间)、claims。createJWT方法内部再进一步的设置加密算法、
员工登录
java
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);
}
微信用户登录
java
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 ≠ 登录框架,而是 无状态认证载体