JWT 是由哪三个部分组成?如何使用JWT进行身份认证?
JWT 是由哪三个部分组成,请写出对应的组成和作用。
JWT(JSON Web Token)是一种轻量级的身份认证令牌,核心由 3个Base64编码的JSON部分 组成,三部分用英文句号(.)连接,格式为 Header.Payload.Signature。
每个部分的组成、作用如下:
1. 第一部分:Header(头部)
组成
- 是一个JSON对象,包含两个核心字段:
alg:指定签名算法(如HS256哈希算法、RS256非对称加密算法),必填;typ:指定令牌类型,固定为JWT,可选(默认即可)。
- 示例(原始JSON):
{"alg": "HS256","typ": "JWT" } - 最终会经过 Base64Url编码 形成令牌的第一部分(注:Base64Url是Base64的变体,适配URL传输,替换了
+//字符)。
作用
- 告诉接收方(如后端服务器):该JWT使用的签名算法是什么,以及令牌类型是JWT,方便接收方解码和验证。
2. 第二部分:Payload(负载/载荷)
组成
- 是一个JSON对象,包含需要传递的核心数据(也叫Claims,“声明”),分为3类:
- 标准声明(可选,约定俗成的通用字段):
iss:令牌签发者(如“xxx系统”);exp:令牌过期时间(时间戳,如1735689600,必填,防止令牌永久有效);iat:令牌签发时间(时间戳);sub:令牌面向的用户(如用户ID);aud:令牌的接收方(如“xxx接口服务”)。
- 自定义声明(业务字段):
- 按需添加的业务数据,如
userId: 1001、role: "admin"、username: "zhangsan"。
- 按需添加的业务数据,如
- 标准声明(可选,约定俗成的通用字段):
- 示例(原始JSON):
{"iss": "user-auth-system","exp": 1735689600,"userId": 1001,"role": "admin" } - 同样经过 Base64Url编码 形成令牌的第二部分。
作用
- 承载身份信息、权限信息或业务数据,实现“无状态认证”——后端无需存储会话,只需解码Payload即可获取用户身份,减少服务器存储压力。
- 注意:Payload是明文编码(Base64Url可反向解码),不能存储敏感数据(如密码、银行卡号)!
3. 第三部分:Signature(签名)
组成
- 不是JSON对象,而是通过 Header指定的算法,对“编码后的Header + 编码后的Payload + 密钥(Secret)”进行加密计算得到的字符串。
- 计算逻辑(以HS256算法为例):
Signature = HMACSHA256(Base64UrlEncode(Header) + "." + Base64UrlEncode(Payload),密钥(如"xxx-2024-secret-key") ) - 最终结果是一串不可反向解密的字符串,作为令牌的第三部分。
作用
- 核心是防止令牌被篡改:接收方(后端)收到JWT后,会用同样的逻辑(相同的Header算法、相同的密钥)重新计算签名,若计算结果与令牌的Signature一致,则说明Header和Payload未被篡改,令牌有效;若不一致,则直接拒绝。
- 密钥的安全性至关重要:对称算法(如HS256)的密钥需仅在签发方和验证方保存;非对称算法(如RS256)用私钥签发、公钥验证,更适合分布式系统。
完整JWT示例(直观理解)
假设上述Header、Payload编码后,加上Signature,最终的JWT令牌如下(三部分用.分隔):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ1c2VyLWF1dGgtc3lzdGVtIiwiZXhwIjoxNzM1Njg5NjAwLCJ1c2VySWQiOjEwMDEsInJvbGUiOiJhZG1pbiJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
关键总结
- 3部分核心逻辑:Header(算法+类型)→ Payload(数据)→ Signature(防篡改);
- 核心价值:无状态、轻量、可跨服务传递身份信息,适合前后端分离、微服务架构的身份认证;
- 安全提醒:Payload明文,不存敏感数据;Signature依赖密钥,密钥绝不能泄露。
如何使用JWT进行身份认证?
使用 JWT 进行身份认证的核心流程是 “签发令牌→携带令牌→验证令牌”,全程无状态(后端不存储会话),适配前后端分离、微服务架构,具体步骤和实操细节如下:
一、核心认证流程(3步走)
1. 第一步:用户登录,后端签发 JWT 令牌
这是唯一需要验证用户账号密码的步骤,验证通过后生成令牌返回给前端。
-
流程细节:
- 前端提交用户凭证(如
username+password)到后端登录接口(如/login); - 后端验证凭证(查询数据库对比账号密码,或对接OAuth2.0等第三方认证);
- 验证通过后,构建 JWT 的 Header(指定算法) 和 Payload(用户ID、角色、过期时间等非敏感数据);
- 用后端保存的 密钥(Secret),按 Header 指定的算法生成 Signature,拼接
Header.Payload.Signature得到完整 JWT; - 后端将 JWT 令牌返回给前端(通常放在响应体
{ "token": "xxx.jwt" })。
- 前端提交用户凭证(如
-
实操示例(Java + Spring Boot):
依赖 JWT 工具包(如io.jsonwebtoken:jjwt),编写签发逻辑:// JWT 工具类(核心方法:生成令牌) public class JwtUtil {// 密钥(必须保密,生产环境用配置文件存储,避免硬编码)private static final String SECRET = "your-strong-secret-key-32bytes+";// 令牌过期时间(如 2 小时,单位:毫秒)private static final long EXPIRATION = 7200000;// 生成 JWT 令牌public static String generateToken(User user) {// 1. 构建 Payload(包含标准声明和自定义业务数据)Map<String, Object> claims = new HashMap<>();claims.put("userId", user.getId()); // 自定义:用户IDclaims.put("role", user.getRole()); // 自定义:用户角色return Jwts.builder().setClaims(claims) // 载荷数据.setIssuedAt(new Date()) // 签发时间.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION)) // 过期时间.signWith(SignatureAlgorithm.HS256, SECRET) // 签名算法 + 密钥.compact(); // 生成最终令牌} }// 登录接口(Controller) @PostMapping("/login") public Result login(@RequestBody LoginDTO loginDTO) {// 1. 验证账号密码(示例:从数据库查询用户)User user = userService.verify(loginDTO.getUsername(), loginDTO.getPassword());if (user == null) {return Result.error("账号或密码错误");}// 2. 生成 JWT 令牌String token = JwtUtil.generateToken(user);// 3. 返回令牌给前端return Result.success("登录成功", Collections.singletonMap("token", token)); }
2. 第二步:前端存储并携带 JWT 令牌
前端拿到令牌后,需在后续请求中携带,让后端识别用户身份。
-
存储方式:
- 短期存储:
localStorage(持久化,关闭浏览器不丢失)或sessionStorage(会话级,关闭浏览器失效); - 注意:避免存储在
cookie中(易受 CSRF 攻击,除非开启HttpOnly和SameSite)。
- 短期存储:
-
携带方式:
前端每次请求后端接口(如查询用户信息、提交订单)时,将 JWT 放在 HTTP 请求头的Authorization字段 中(行业标准),格式为:Authorization: Bearer {你的JWT令牌}(注:
Bearer后有一个空格,不可省略) -
实操示例(前端 Vue/React):
// 1. 登录成功后存储令牌(Vue示例) login() {axios.post("/login", { username: "zhangsan", password: "123456" }).then(res => {const token = res.data.data.token;localStorage.setItem("jwtToken", token); // 存储到localStorage}); }// 2. 全局请求拦截器(自动携带令牌) axios.interceptors.request.use(config => {const token = localStorage.getItem("jwtToken");if (token) {config.headers.Authorization = `Bearer ${token}`; // 添加请求头}return config; });
3. 第三步:后端验证 JWT 令牌,授权访问
后端对需要身份认证的接口,统一拦截并验证 JWT 合法性,验证通过则放行,否则拒绝请求。
-
验证核心逻辑:
- 从请求头
Authorization中提取 JWT 令牌(去掉Bearer前缀); - 验证令牌有效性:
- 签名是否合法(用相同密钥和算法重新计算签名,对比是否一致,防止篡改);
- 令牌是否过期(检查
exp字段的时间戳); - 可选验证:签发者(
iss)、接收方(aud)是否匹配(防止令牌被滥用);
- 验证通过后,从 Payload 中提取用户信息(如
userId、role),存入上下文(如ThreadLocal); - 后续业务逻辑可直接从上下文获取用户信息(如查询当前用户的订单)。
- 从请求头
-
实操示例(Java + Spring Boot 拦截器):
用 Spring 的HandlerInterceptor或Filter实现全局令牌验证:// 1. JWT 验证拦截器 public class JwtAuthInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// (1)提取令牌:从 Authorization 头获取String authHeader = request.getHeader("Authorization");if (authHeader == null || !authHeader.startsWith("Bearer ")) {// 无令牌,返回401未授权response.setStatus(HttpStatus.UNAUTHORIZED.value());response.getWriter().write("请先登录");return false;}String token = authHeader.substring(7); // 去掉 "Bearer " 前缀try {// (2)验证令牌有效性(签名 + 过期时间)Claims claims = Jwts.parser().setSigningKey(JwtUtil.SECRET) // 用相同密钥验证.parseClaimsJws(token) // 解析令牌.getBody(); // 获取Payload数据// (3)将用户信息存入上下文(供后续接口使用)Long userId = claims.get("userId", Long.class);String role = claims.get("role", String.class);UserContext.setUserId(userId); // ThreadLocal存储UserContext.setRole(role);return true; // 验证通过,放行} catch (ExpiredJwtException e) {// 令牌过期response.setStatus(HttpStatus.UNAUTHORIZED.value());response.getWriter().write("令牌已过期,请重新登录");} catch (SignatureException e) {// 签名非法(令牌被篡改)response.setStatus(HttpStatus.FORBIDDEN.value());response.getWriter().write("令牌无效");} catch (Exception e) {response.setStatus(HttpStatus.UNAUTHORIZED.value());response.getWriter().write("身份认证失败");}return false;}// 接口执行完后清理ThreadLocal(避免内存泄漏)@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {UserContext.clear();} }// 2. 注册拦截器(Spring Boot配置) @Configuration public class WebConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new JwtAuthInterceptor()).addPathPatterns("/**") // 所有接口都拦截.excludePathPatterns("/login") // 排除登录接口(无需认证).excludePathPatterns("/register") // 排除注册接口.excludePathPatterns("/static/**"); // 排除静态资源} }// 3. 业务接口(直接从上下文获取用户信息) @GetMapping("/user/info") public Result getUserInfo() {Long userId = UserContext.getUserId(); // 无需从请求参数获取,直接从上下文拿User user = userService.getById(userId);return Result.success(user); }
二、关键优化:解决 JWT 痛点
JWT 本身是“一旦签发无法撤回”的,需通过以下方式解决核心痛点:
1. 令牌过期与刷新机制
- 问题:令牌过期后用户需重新登录,体验差;
- 方案:引入 访问令牌(Access Token)+ 刷新令牌(Refresh Token):
- 访问令牌:短期有效(如 2 小时),用于接口访问,过期快,风险低;
- 刷新令牌:长期有效(如 7 天),仅用于获取新的访问令牌,存储在更安全的地方(如
HttpOnly Cookie); - 流程:访问令牌过期时,前端用刷新令牌调用
/refresh-token接口,后端验证刷新令牌有效后,返回新的访问令牌,用户无需重新登录。
2. 令牌黑名单(应对注销/账号冻结)
- 问题:用户注销或账号被冻结后,已签发的未过期令牌仍能使用;
- 方案:维护“令牌黑名单”(存储已注销/失效的令牌):
- 用 Redis 存储黑名单,key 为令牌,value 为过期时间(与 JWT 的
exp一致); - 验证令牌时,先检查是否在黑名单中,若在则直接拒绝;
- 优势:Redis 高性能,支持过期自动删除,不占用大量内存。
- 用 Redis 存储黑名单,key 为令牌,value 为过期时间(与 JWT 的
3. 安全加固
- 密钥安全:生产环境用长密钥(如 32 字节以上),存储在配置中心(如 Nacos、Apollo),避免硬编码;
- 算法选择:分布式系统用非对称算法(如 RS256),私钥签发令牌,公钥验证,避免密钥泄露风险;
- Payload 安全:绝不存储敏感数据(密码、银行卡号),仅存非敏感身份信息(用户ID、角色);
- HTTPS 传输:所有请求用 HTTPS,防止令牌被中间人窃取。
三、适用场景与不适用场景
适用场景:
- 前后端分离架构(Vue/React + Spring Boot);
- 微服务架构(跨服务身份认证,无需共享会话);
- 无状态服务(降低服务器存储压力,便于水平扩展)。
不适用场景:
- 需即时撤回令牌的场景(如银行转账,需实时禁用账号);
- 敏感数据传输(Payload 可解码,无法加密);
- 会话需关联复杂状态的场景(如购物车未登录状态保持)。
总结
JWT 身份认证的核心是“无状态、轻量级、跨服务”,核心流程可概括为:
- 登录 → 后端验证凭证 → 签发 JWT;
- 后续请求 → 前端携带 JWT;
- 后端拦截 → 验证 JWT → 授权访问。
通过“刷新令牌+黑名单+HTTPS”可解决其核心痛点,是目前前后端分离、微服务架构的首选身份认证方案。
JWT的Payload部分是否可以包含敏感信息?
不可以,JWT 的 Payload 部分绝对不能包含密码、银行卡号、手机号等敏感信息。
核心原因
- Payload 是明文编码而非加密:Payload 仅通过 Base64Url 编码(不是加密算法)处理,任何人获取到 JWT 令牌后,都能通过 Base64Url 解码工具直接还原出原始数据,无任何保密性可言。
- 令牌传输易泄露:JWT 通常通过 HTTP 请求头、localStorage 等方式传输和存储,可能被中间人窃取或通过前端漏洞获取,敏感信息会直接暴露。
示例验证
假设 Payload 包含手机号 13800138000,原始 JSON 如下:
{ "userId": 1001, "phone": "13800138000" }
Base64Url 编码后为 eyJ1c2VySWQiOjEwMDEsInBob25lIjoiMTM4MDAxMzgwMDAifQ,通过在线解码工具可直接还原出手机号,毫无隐私保护。
正确做法
- Payload 仅存非敏感身份/权限信息:如用户 ID、角色、用户名(非隐私字段)、令牌过期时间等,用于后端识别用户身份和授权。
- 敏感信息存储在后端:敏感数据仅保存在数据库或安全的缓存(如加密后的 Redis)中,后端通过 Payload 中的用户 ID 等非敏感信息,查询获取敏感数据。
- 需传输敏感数据时单独加密:若业务必须传输敏感信息,需额外通过 AES 等加密算法加密后,再作为业务参数传递(不放入 Payload)。
