SpringBoot--JWT

发布于:2025-08-19 ⋅ 阅读:(15) ⋅ 点赞:(0)

一、JWT 的简单了解

1. 什么是 JWT?

JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在 各方之间安全地传输信息。它基于 JSON 格式,信息通过 数字签名 的方式保证不可篡改,常用于 身份认证和信息交换


2. JWT 的结构

JWT 由三部分组成,每部分用 . 分隔:

Header.Payload.Signature

(1) Header(头部)

描述 类型签名算法。例如:

{
  "alg": "HS256",
  "typ": "JWT"
}

Base64Url 编码后得到第一部分。


(2) Payload(载荷)

存放 声明(Claims) 的地方。
声明分三类:

  • 注册声明(Registered Claims):建议但非必须使用的标准字段

    • iss:签发者 (issuer)

    • sub:主题 (subject)

    • aud:接收者 (audience)

    • exp:过期时间 (expiration time)

    • nbf:生效时间 (not before)

    • iat:签发时间 (issued at)

    • jti:JWT ID,用于防止重放攻击

  • 公共声明(Public Claims):大家约定好的字段

  • 私有声明(Private Claims):系统内部定义的字段,比如 userIdrole

例如:

{
  "sub": "user123",
  "name": "Alice",
  "role": "admin",
  "exp": 1692345600
}

Base64Url 编码后得到第二部分。


(3) Signature(签名)

用于防篡改。
计算方法:

HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret
)

即:把前两部分拼接,用密钥和算法加密生成签名。


3. JWT 的认证流程

  1. 用户登录:客户端提交用户名、密码。

  2. 服务端验证:验证通过后,生成 JWT(带用户信息、过期时间等),返回给客户端。

  3. 客户端存储:通常存储在 LocalStorage 或 Cookie 中。

  4. 后续请求:客户端请求时在 Header 中带上 Authorization: Bearer <token>

  5. 服务端验证:服务端用密钥验证 JWT 是否有效、是否过期、是否被篡改。

  6. 验证通过:允许访问资源。


4. JWT 的优点

  • 无状态:服务端不存储 Session,支持分布式扩展。

  • 跨语言:JSON 格式,兼容性强。

  • 高性能:减少数据库查询,认证速度快。

  • 信息完整:载荷可携带用户信息,减少额外查询。


5. JWT 的缺点

  • 不可撤销:一旦签发,在过期前无法主动让其失效(除非维护黑名单)。

  • 体积较大:比 Session ID 更大(因为要携带签名和用户信息)。

  • 安全风险:如果私钥泄露,所有 token 都会失效。

   5.1 为什么说不可撤销呢?在前端删除jwt(前端销毁)不就好了?

         用户自己点击 “退出登录”,前端删除 token,下次再访问接口时就没有 token 了 → 确实相当于退出了

         在大多数 正常用户行为 下,这种方式足够。

 5.1.1 但是会有特殊情况:

(1)多端登录 / 被动下线

    🔹如果一个账号在两台设备登录,管理员希望强制其中一台下线。

    🔹单靠前端删 token,另一台设备的 token 依然有效。
    
(2)token 泄露

    🔹假如 token 被别人拷贝了(比如被窃取、抓包、XSS 攻击),那个人依然可以继续用。

    🔹你本地删掉 token 没用,因为“坏人”还有副本。

(3)黑名单/封禁需求

    🔹如果公司后台管理员封禁了某个账号,这个用户的 token 在过期前仍然能请求接口。

    🔹只能靠服务端来控制 token 的立即失效。

    5.2 解决办法:

        具体实现本文不做演示,着重介绍JWT

  • 前端销毁:保证用户主动退出后,浏览器不再携带 token。

  • 后端控制:保证遇到异常场景(黑名单、泄露、多端冲突)时,能强制失效。

    • 方式一:Redis 黑名单

    • 方式二:Redis 白名单(只保留最新 token)

    • 方式三:短 token + refresh token


6. 使用场景

  • 用户认证(替代传统 session)。

  • API 鉴权(前后端分离、微服务)。

  • 信息安全传输(声明不可篡改)。


二、Spring Boot 使用 JWT 的完整示例

这里我们用 jjwt 库(io.jsonwebtoken:jjwt-api)来实现。


1. 添加依赖(Maven)

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.5</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId> <!-- 支持 json 序列化 -->
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>

2. 工具类 JwtUtil

import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;

import java.security.Key;
import java.util.Date;

public class JwtUtil {
    // 私钥(实际项目中放到配置文件里)
    private static final Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);

    // 生成 token
    public static String generateToken(String userId, String role) {
        long nowMillis = System.currentTimeMillis();
        long expMillis = nowMillis + 3600000; // 1小时过期
        Date exp = new Date(expMillis);

        return Jwts.builder()
                .setSubject(userId) // sub
                .claim("role", role) // 自定义字段
                .setIssuedAt(new Date(nowMillis)) // iat
                .setExpiration(exp) // exp
                .signWith(key) // 签名
                .compact();
    }

    // 验证 token
    public static boolean validateToken(String token) {
        try {
            Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
            return true;
        } catch (JwtException e) {
            return false;
        }
    }

    // 获取用户ID
    public static String getUserId(String token) {
        Claims claims = Jwts.parserBuilder().setSigningKey(key).build()
                .parseClaimsJws(token).getBody();
        return claims.getSubject();
    }

    // 获取角色
    public static String getUserRole(String token) {
        Claims claims = Jwts.parserBuilder().setSigningKey(key).build()
                .parseClaimsJws(token).getBody();
        return claims.get("role", String.class);
    }
}

3. Controller 示例

import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/auth")
public class AuthController {

    // 模拟登录接口
    @PostMapping("/login")
    public String login(@RequestParam String username, @RequestParam String password) {
        // 假设用户名=admin, 密码=123456
        if ("admin".equals(username) && "123456".equals(password)) {
            // 登录成功,生成 JWT
            return JwtUtil.generateToken("1001", "admin");
        }
        return "用户名或密码错误";
    }

    // 需要鉴权的接口
    @GetMapping("/check")
    public String check(@RequestHeader("Authorization") String token) {
        token = token.replace("Bearer ", "");
        if (JwtUtil.validateToken(token)) {
            return "用户ID:" + JwtUtil.getUserId(token) +
                   ",角色:" + JwtUtil.getUserRole(token);
        }
        return "token 无效或已过期";
    }
}

4. 请求示例

(1) 登录获取 Token

POST http://localhost:8080/auth/login?username=admin&password=123456

返回:

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMDAxIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNjkyMzQ1NjAwLCJleHAiOjE2OTIzNDkyMDB9.Tk7Q7jtwgm3d...

(2) 携带 Token 访问接口

GET http://localhost:8080/auth/check
Header: Authorization: Bearer <上一步返回的token>

返回:

用户ID:1001,角色:admin

✅ 总结:

  • JWT 由 头部 + 载荷 + 签名 组成。

  • 使用时,客户端存储 token 并在请求头中传递。

  • Spring Boot 中可以通过 工具类 + 拦截器/过滤器 来实现鉴权。


网站公告

今日签到

点亮在社区的每一天
去签到