万金流
以码会友。 吾Q:578751655。 水平有限,轻喷,谢!

JWT原理--与语言无关

普通JWT整体结构

JWT 字符串长这样,三段用 . 分隔:

header.payload.signature

每一段都是 Base64URL 编码(不是加密!只是编码,可逆,随便解码看内容)

Base64URL = Base64 的安全变体:把 +→-,/→_,去掉末尾 =,适合放在 URL、请求头里。解码不需要密钥,任何人都能解。

第1段:Header(JSON 对象)

这是第一个 JSON,描述「用什么算法做签名」
示例 JSON:

{
  "alg": "HS256",
  "typ": "JWT"
}
  • alg:签名算法
  • typ:固定写 JWT,标识类型

👉 变换过程:
JSON → UTF-8字节 → Base64URL 编码 → 第一段字符串

第2段:Payload(载荷,业务 JSON 对象,重点!)

业务数据,也是大家最关心的 JSON。
分为两类字段:标准声明(保留字段) + 自定义字段
示例 JSON:

{
  "sub": "user_10086",
  "name": "张三",
  "exp": 1760000000,
  "iat": 1759990000,
  "role": "admin"
}

标准常用声明(可选,不是必须写):

  • sub:主题,一般用户ID
  • iat:issued at,签发时间(时间戳)
  • exp:expiration,过期时间(重点!服务器校验这个)
  • iss:issuer,签发者
  • aud:audience,接收方

✅ 重点:Payload 是明文!Base64URL 只是编码,前端拿到 JWT 可以直接解码看到里面所有内容
❌ 不要放密码、银行卡、隐私敏感数据!

👉 变换过程:
Payload JSON → UTF-8字节 → Base64URL 编码 → 第二段字符串

第3段:Signature(签名,即我们以前讲的验证字符串)

签名计算规则:

签名 = 签名算法(Base64URL(header) + "." + Base64URL(payload), 密钥)

拿 HS256(HMAC-SHA256)举例:

  1. 拼接串:base64Header.base64Payload
  2. 使用密钥,对这个拼接字符串做 HMAC-SHA256 哈希
  3. 把哈希结果字节做 Base64URL 编码 → 第三段字符串

签发完整流程(生成JWT)

输入:Header JSON + Payload JSON + 密钥

  1. Header JSON → base64url → s1
  2. Payload JSON → base64url → s2
  3. 拼接 s1.s2,用密钥+指定算法算出签名 → s3
  4. 最终 JWT = s1.s2.s3

全程:s1、s2 任何人都能解码还原回原始 JSON;只有 s3 依赖密钥。

JWT在Spring-boot-web中的使用--cookie版

普通字符串验证版本,可以自己扩展。

借助本博客《Spring_boot_web中的Session与拦截器鉴权》里的前端,修改后端内容。

pom.xml中添加依赖:

<dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.12.6</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.12.6</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>0.12.6</version>
            <scope>runtime</scope>
        </dependency>

添加jwt的工具类JwtUtil.java:

package com.example.demo_web.Util;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;

import java.nio.charset.StandardCharsets;
import java.util.Date;

@Component
public class JwtUtil
{
    // HS256 要求密钥长度 ≥ 32 字节(256位)
    private final SecretKey secretKey = Keys
            .hmacShaKeyFor("abcdefghijklmnopqrstuvwxyz123456".getBytes(StandardCharsets.UTF_8));
    private final long expireMs = 3600 * 1000L;

    // 签发 JWT
    public String generate(String userId, int level)
    {
        long now = System.currentTimeMillis();
        return Jwts.builder().subject(userId) // 用户标识
                .claim("level", level) // 自定义业务字段,权限等级
                .issuedAt(new Date(now)) // 签发时间
                .expiration(new Date(now + expireMs)) // 过期时间
                // 上面按需要写,下面两行是签名和拼接成jwt字符串
                .signWith(secretKey, Jwts.SIG.HS256) // 显式指定签名算法
                .compact();
    }

    // 验签 + 解析,直接返回载荷
    // 对于返回的Claims:
    // claims.getSubject(); // 拿 sub
    // claims.get("level", String.class); // 拿自定义字段
    // claims.getExpiration(); // 拿过期时间
    //验签失败,抛出异常
    public Claims parse(String token)
    {
        return Jwts.parser()// 创建空解析器
                .verifyWith(secretKey)// 设置密码
                .build()// 生成解析器
                .parseSignedClaims(token)// 拆三段 + 验签 + 解码
                .getPayload();// 获取负载
    }
}

修改控制器My_Controller.java

package com.example.demo_web.Controllers;

import java.util.Map;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import com.example.demo_web.Util.JwtUtil;

import io.jsonwebtoken.Claims;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

@RestController
public class My_Controller
{
    private static final String TOKEN_COOKIE = "access_token";
    private final JwtUtil jwtUtil;

    public My_Controller(JwtUtil jwtUtil)
    {
        this.jwtUtil = jwtUtil;
    }

    @PostMapping("/login")
    public String log_In(@RequestBody Map<String, String> body, HttpServletResponse response)
    {
        String username = body.get("username");
        if (username.length() > 4)
        {
            String jwt = jwtUtil.generate(username, 8);

            Cookie cookie = new Cookie(TOKEN_COOKIE, jwt);
            cookie.setHttpOnly(true); // 核心:JS 无法读取,防XSS
            cookie.setSecure(false); // 本地HTTP,生产HTTPS设true
            cookie.setPath("/"); //访问服务器任意路径,都携带此cookie
            cookie.setMaxAge(3600); // Cookie有效期,与JWT过期时间对齐
            cookie.setAttribute("SameSite", "Lax"); // 基础防CSRF
            response.addCookie(cookie);

            return "OK";
        }
        else
        {
            //以下代码也可以用于登出(清除指定cookie)
            Cookie cookie = new Cookie(TOKEN_COOKIE, "");
            cookie.setHttpOnly(true);
            cookie.setSecure(false); 
            cookie.setPath("/");
            cookie.setMaxAge(0);
            response.addCookie(cookie);

            return "No";
        }
    }

    @GetMapping("getstring")
    public String getSth(HttpServletRequest request)
    {
        String token = null;
        if (request.getCookies() != null)
        {
            for (Cookie cookie : request.getCookies())
            {
                if ("access_token".equals(cookie.getName()))
                {
                    token = cookie.getValue();
                    break;
                }
            }
        }

        if (token == null)
        {
            return "未登录";
        }

        // 验签 + 解析
        try
        {
            Claims claims = jwtUtil.parse(token);
            return "用户:" + claims.getSubject() + "权限:" + claims.get("level", Integer.class) + "张三家的大黑狗昨天下河游泳了";
        } catch (Exception e)
        {
            // 签名错误、过期、格式非法都会抛出异常
            return "Token无效或已过期";
        }
    }
}

至此,使用cookie做载体的jwt可以正常工作。

在浏览器开发者工具(F12)的应用->cookie里可以看到名为"access_token"的cookie值,形如eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhbGljZSIsImxldmVsIjo4LCJpYXQiOjE3OTI4NTY0NDgsImV4cCI6MTc5Mjg2MDA0OH0.FqKvx68uUzU-D23Zj9-vAd7eZC5nMOxz6DYNXRo5Zok,HttpOnly是勾上的。

鸣谢AI豆老师、元老师和智老师。

posted on 2026-10-24 23:52  万金流  阅读(5)  评论(0)    收藏  举报