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: 1001role: "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 令牌

这是唯一需要验证用户账号密码的步骤,验证通过后生成令牌返回给前端。

  • 流程细节

    1. 前端提交用户凭证(如 username + password)到后端登录接口(如 /login);
    2. 后端验证凭证(查询数据库对比账号密码,或对接OAuth2.0等第三方认证);
    3. 验证通过后,构建 JWT 的 Header(指定算法)Payload(用户ID、角色、过期时间等非敏感数据)
    4. 用后端保存的 密钥(Secret),按 Header 指定的算法生成 Signature,拼接 Header.Payload.Signature 得到完整 JWT;
    5. 后端将 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()); // 自定义:用户ID
      claims.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 攻击,除非开启 HttpOnlySameSite)。
  • 携带方式
    前端每次请求后端接口(如查询用户信息、提交订单)时,将 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 合法性,验证通过则放行,否则拒绝请求。

  • 验证核心逻辑

    1. 从请求头 Authorization 中提取 JWT 令牌(去掉 Bearer 前缀);
    2. 验证令牌有效性:
      • 签名是否合法(用相同密钥和算法重新计算签名,对比是否一致,防止篡改);
      • 令牌是否过期(检查 exp 字段的时间戳);
      • 可选验证:签发者(iss)、接收方(aud)是否匹配(防止令牌被滥用);
    3. 验证通过后,从 Payload 中提取用户信息(如 userIdrole),存入上下文(如 ThreadLocal);
    4. 后续业务逻辑可直接从上下文获取用户信息(如查询当前用户的订单)。
  • 实操示例(Java + Spring Boot 拦截器)
    用 Spring 的 HandlerInterceptorFilter 实现全局令牌验证:

    // 1. JWT 验证拦截器
    public class JwtAuthInterceptor implements HandlerInterceptor {
    @Override
    public 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(避免内存泄漏)
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    UserContext.clear();
    }
    }
    // 2. 注册拦截器(Spring Boot配置)
    @Configuration
    public class WebConfig implements WebMvcConfigurer {
    @Override
    public 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 高性能,支持过期自动删除,不占用大量内存。
3. 安全加固
  • 密钥安全:生产环境用长密钥(如 32 字节以上),存储在配置中心(如 Nacos、Apollo),避免硬编码;
  • 算法选择:分布式系统用非对称算法(如 RS256),私钥签发令牌,公钥验证,避免密钥泄露风险;
  • Payload 安全:绝不存储敏感数据(密码、银行卡号),仅存非敏感身份信息(用户ID、角色);
  • HTTPS 传输:所有请求用 HTTPS,防止令牌被中间人窃取。

三、适用场景与不适用场景

适用场景:
  • 前后端分离架构(Vue/React + Spring Boot);
  • 微服务架构(跨服务身份认证,无需共享会话);
  • 无状态服务(降低服务器存储压力,便于水平扩展)。
不适用场景:
  • 需即时撤回令牌的场景(如银行转账,需实时禁用账号);
  • 敏感数据传输(Payload 可解码,无法加密);
  • 会话需关联复杂状态的场景(如购物车未登录状态保持)。

总结

JWT 身份认证的核心是“无状态、轻量级、跨服务”,核心流程可概括为:

  1. 登录 → 后端验证凭证 → 签发 JWT;
  2. 后续请求 → 前端携带 JWT;
  3. 后端拦截 → 验证 JWT → 授权访问。

通过“刷新令牌+黑名单+HTTPS”可解决其核心痛点,是目前前后端分离、微服务架构的首选身份认证方案。

JWT的Payload部分是否可以包含敏感信息?

不可以,JWT 的 Payload 部分绝对不能包含密码、银行卡号、手机号等敏感信息。

核心原因

  1. Payload 是明文编码而非加密:Payload 仅通过 Base64Url 编码(不是加密算法)处理,任何人获取到 JWT 令牌后,都能通过 Base64Url 解码工具直接还原出原始数据,无任何保密性可言。
  2. 令牌传输易泄露:JWT 通常通过 HTTP 请求头、localStorage 等方式传输和存储,可能被中间人窃取或通过前端漏洞获取,敏感信息会直接暴露。

示例验证

假设 Payload 包含手机号 13800138000,原始 JSON 如下:

{ "userId": 1001, "phone": "13800138000" }

Base64Url 编码后为 eyJ1c2VySWQiOjEwMDEsInBob25lIjoiMTM4MDAxMzgwMDAifQ,通过在线解码工具可直接还原出手机号,毫无隐私保护。

正确做法

  1. Payload 仅存非敏感身份/权限信息:如用户 ID、角色、用户名(非隐私字段)、令牌过期时间等,用于后端识别用户身份和授权。
  2. 敏感信息存储在后端:敏感数据仅保存在数据库或安全的缓存(如加密后的 Redis)中,后端通过 Payload 中的用户 ID 等非敏感信息,查询获取敏感数据。
  3. 需传输敏感数据时单独加密:若业务必须传输敏感信息,需额外通过 AES 等加密算法加密后,再作为业务参数传递(不放入 Payload)。
posted @ 2025-12-01 09:50  yangykaifa  阅读(0)  评论(0)    收藏  举报