JsonWeb token(JWT)跨域认证

传统的session:

  1. 核心原理 服务端创建唯一 Session ,生成专属 SessionID;登录成功后,把 SessionID 通过 Cookie 发给浏览器保存。

  2. 访问流程

  • 简单、安全,数据存在服务端,前端无法篡改用户信息
  • 有状态:服务端需要保存会话,集群部署需做 session 共享
  • 过期、注销:服务端销毁 session 即可强制下线

特点

  • 会话数据(用户信息、登录状态):存在服务端(内存 / Redis / 数据库)
  • 唯一标识 SessionID:存在客户端 Cookie

存储位置

  • 客户端后续每次请求,浏览器自动携带 Cookie 里的 SessionID
  • 服务端根据 SessionID 查找对应的会话数据,识别用户、判断登录状态、权限

弊端:若服务器采用集群存储,用户每次访问的服务器端可能都不同,可能上次访问的session存储在服务器A,这次访问服务器B,数据难以同步

token则是将信息存储在本地浏览器中,每次访问服务器会读取本地浏览器的token。

简单理解:token相当于工牌,门卫看到就放行。session是门卫认识你就放行,但换了门卫就不行了

为了防止用户本地篡改信息,JWT提供了签名

JWT 格式:头部。载荷。签名 Header.Payload.Signature 三部分用小数点拼接


1. Header 头部(头)

  • 内容:加密算法、令牌类型
  • 示例:alg: HS256(哈希加密)、typ: JWT
  • 格式:Base64 编码

2. Payload 载荷(主体)

  • 业务数据 (核心)
    • 过期时间、签发时间、用户 ID、角色、权限
  • ⚠️可被解码查看,不能放密码、敏感数据
  • 格式:Base64 编码

3. Signature 签名(防伪)

  • 规则:头部+载荷 + 密钥 加密算出
  • 作用:防止篡改只要头部、载荷被改,签名校验失败,令牌直接作废
  • 只有后端持有密钥,安全

token的配置:

复制代码
// 7天过期(单位:秒)
private static Long expire = 604800L;

// 32位秘钥(这里示例用的是40位字符串,HS512算法要求密钥长度足够)
private static String secret = "abcdfghiabcdfghiabcdfghiabcdfghi";

/**
 * 生成JWT Token
 * @param username 用户名,作为Token的主题(Subject)
 * @return 生成的JWT字符串
 */
public static String generateToken(String username) {
    // 1. 获取当前时间
    Date now = new Date();
    
    // 2. 计算Token过期时间:当前时间 + 1000(毫秒/秒) * expire(秒)
    //    也就是当前时间往后推 604800 秒(7天)
    Date expiration = new Date(now.getTime() + 1000 * expire);
    
    // 3. 构建JWT并返回
    return Jwts.builder()
        // 设置Header中的自定义参数:type=JWT
        .setHeaderParam("type", "JWT")
        // 设置Payload中的sub(主题):这里存的是用户名
        .setSubject(username)
        // 设置Payload中的iat(签发时间):当前时间
        .setIssuedAt(now)
        // 设置Payload中的exp(过期时间):前面计算好的expiration
        .setExpiration(expiration)
        // 使用HS512算法和秘钥对JWT进行签名
        .signWith(SignatureAlgorithm.HS512, secret)
        // 压缩并生成最终的JWT字符串
        .compact();
}

解析token:

复制代码
// 解析token,获取Token中的Claims(载荷信息)
public static Claims getClaimsByToken(String token) {
    return Jwts.parser()          // 1. 创建JWT解析器
        .setSigningKey(secret)    // 2. 设置签名密钥,用于验证Token的签名是否合法
        .parseClaimsJws(token)    // 3. 解析并验证Token,解析失败会抛出异常(如过期、篡改等)
        .getBody();               // 4. 获取解析后的载荷部分(包含用户信息、签发时间、过期时间等)
}

token检验:

复制代码
 public static boolean verifyToken(String token) {
        try {
            getClaimsByToken(token);
            return true;
        } catch (ExpiredJwtException e) {
            // token过期
            System.out.println("token已过期");
        } catch (SignatureException e) {
            // 签名错误、token被篡改
            System.out.println("token签名非法");
        } catch (Exception e) {
            System.out.println("token解析失败");
        }
        return false;
    }

controller实现类:

复制代码
package com.example.jwtdemo;

import com.example.jwtdemo.jwt.JwtConfig;
import io.jsonwebtoken.Claims;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;

/**
 * JWT 测试控制类
 * 无前端,直接用 ApiPost 调接口
 */
@RestController
@RequestMapping("/jwt")
public class JwtController {

    /**
     * 1. 登录接口:传入用户名,返回Token
     * 地址:GET  http://localhost:8080/jwt/login?username=zhangsan
     */
    @GetMapping("/login")
    public String login(@RequestParam String username) {
        // 模拟登录成功,下发token
        String token = JwtConfig.generateToken(username);
        return "登录成功,你的Token:" + token;
    }

    /**
     * 2. 解析token接口
     * 地址:GET  http://localhost:8080/jwt/parse?token=xxx
     */
    @GetMapping("/parse")
    public String parseToken(@RequestParam String token) {
        boolean flag = JwtConfig.verifyToken(token);
        if (!flag) {
            return "Token非法或已失效";
        }
        Claims claims = JwtConfig.getClaimsByToken(token);
        String username = claims.getSubject();
        Date expire = claims.getExpiration();
        return "解析成功:\n用户名:" + username + "\n过期时间:" + expire;
    }

    /**
     * 3. 需要携带Token才能访问的受保护接口
     * http://localhost:8080/jwt/auth?token=xxx
     */
    @GetMapping("/auth")
    public String authApi(@RequestParam String token) {
        if (!JwtConfig.verifyToken(token)) {
            return "权限不足,请先登录!";
        }
        Claims claims = JwtConfig.getClaimsByToken(token);
        return "访问受保护资源成功,当前登录用户:" + claims.getSubject();
    }
}

apipost中调用get方法输入路径:http://localhost:8080/jwt/login?username=zhangsan

复制token后输入解析路径中:

http://localhost:8080/jwt/parse?token=eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFM1MTIifQ.eyJzdWIiOiJ6aGFuZ3NhbiIsImlhdCI6MTc3Njk1MTE5OSwiZXhwIjoxNzc3NTU1OTk5fQ.pxNS260smI3sPonZpHy8dXcJ1IkRZT4M2G95zH0zTnzMO4vlHWZQackM7uNOoUp_v9nR-GZhau8EmyF1nn5EiQ

相关推荐
叼烟扛炮2 小时前
【C 语言系统入门教程】第 19 讲:数据在内存中的存储 | 零基础学习笔记
c语言·学习·数据存储·原码反码补码·大小端字节序·浮点数存储
斯维赤2 小时前
Python学习超简单第八弹:网络编程
网络·python·学习
lUie INGA10 小时前
在2023idea中如何创建SpringBoot
java·spring boot·后端
handler0111 小时前
从源码到二进制:深度拆解 Linux 下 C 程序的编译与链接全流程
linux·c语言·开发语言·c++·笔记·学习
电子云与长程纠缠11 小时前
UE5 两种方式解决Decal Actor贴花拉伸问题
学习·ue5·游戏引擎
geBR OTTE11 小时前
SpringBoot中整合ONLYOFFICE在线编辑
java·spring boot·后端
red_redemption11 小时前
自由学习记录(172)
学习·cache line 64b·重用距离
阿荻在肝了12 小时前
Agent学习六:LangGraph学习-持久化与记忆一
python·学习·agent
of Watermelon League12 小时前
SpringBoot集成Flink-CDC,实现对数据库数据的监听
数据库·spring boot·flink