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

相关推荐
李日华大战鸡红4 小时前
FOC状态空间方程模型推导(学习记录)
python·学习·线性代数
省钱兄--zs5 小时前
西安24小时自助健身房解决方案实战:系统开发与部署全流程指南
java·spring boot·系统架构·intellij-idea·需求分析
kkkkkkkkkk_Z6 小时前
新手自学嵌入式 | 学习日记:异常与中断基础、i.MX6ULL裸机开发环境搭建、GPIO模块与点灯原理
学习
m4Rk_7 小时前
【论文阅读】Agent 记忆机制(94):MemGen——在推理过程中动态生成并织入潜在记忆
论文阅读·人工智能·学习·开源·github
晓德7 小时前
0、LLM大模型安全学习系列(启)
学习·安全
ly76897 小时前
Spring Boot 集成 Elasticsearch 的生产实践:客户端选型、索引生命周期与批量写入容错
spring boot·elasticsearch·索引生命周期·java api client·批量写入
程序猿_极客7 小时前
【免费】分享一套优质的基于SpringBoot的服装商城管理系统的设计与实现(带可视化图表、协同过滤功能),源码+文档+视频详解(讲解)
java·spring boot·后端·服装商城管理系统
Titan20248 小时前
MySQL索引学习笔记
笔记·学习·mysql
JWASX8 小时前
Java 转 go 学习 - kitex(5)
学习·golang
bug菌8 小时前
🤔同事突然问我:Spring的注解 @Component 和 @Service 有何不同?
java·spring boot·后端