JWT 是由哪三个部分组成?如何使用JWT进行身份认证?

JWT 是由哪三个部分组成,请写出对应的组成和作用。

JWT(JSON Web Token)是一种轻量级的身份认证令牌,核心由 3个Base64编码的JSON部分 组成,三部分用英文句号(.)连接,格式为 Header.Payload.Signature

每个部分的组成、作用如下:

1. 第一部分:Header(头部)

组成
  • 是一个JSON对象,包含两个核心字段:

    • alg:指定签名算法(如 HS256 哈希算法、RS256 非对称加密算法),必填;
    • typ:指定令牌类型,固定为 JWT,可选(默认即可)。
  • 示例(原始JSON):

    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):

    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),编写签发逻辑:

    java 复制代码
    // 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 字段 中(行业标准),格式为:

    http 复制代码
    Authorization: Bearer {你的JWT令牌}

    (注:Bearer 后有一个空格,不可省略)

  • 实操示例(前端 Vue/React)

    javascript 复制代码
    // 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 实现全局令牌验证:

    java 复制代码
    // 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 如下:

json 复制代码
{ "userId": 1001, "phone": "13800138000" }

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

正确做法

  1. Payload 仅存非敏感身份/权限信息:如用户 ID、角色、用户名(非隐私字段)、令牌过期时间等,用于后端识别用户身份和授权。
  2. 敏感信息存储在后端:敏感数据仅保存在数据库或安全的缓存(如加密后的 Redis)中,后端通过 Payload 中的用户 ID 等非敏感信息,查询获取敏感数据。
  3. 需传输敏感数据时单独加密:若业务必须传输敏感信息,需额外通过 AES 等加密算法加密后,再作为业务参数传递(不放入 Payload)。
相关推荐
程序员陆通6 小时前
CentOS/AlmaLinux 9 中 SSH 服务启动失败:OpenSSL 版本不匹配解决
linux·centos·ssh
noravinsc6 小时前
https 可以访问 8866端口吗
网络协议·http·https
z10_146 小时前
多协议网关架构
架构
岛屿旅人6 小时前
英国国防部推进本土化开放架构建设
网络·人工智能·安全·web安全·架构
Unstoppable226 小时前
八股训练营第 6 天 | HTTPS 和HTTP 有哪些区别?HTTPS的工作原理(HTTPS建立连接的过程)?TCP和UDP的区别?
tcp/ip·http·https·八股
称心-如意7 小时前
浅谈TCP与UDP协议:TCP和UDP分别是什么,区别在哪里
网络协议·tcp/ip·udp
sas-soft7 小时前
短任务占资源、低频 API 耗成本?混合架构方案:微服务扛核心,Serverless 接弹性需求
微服务·架构·serverless