JWT和拦截器使用【附Maven中操作步骤】

JWT

定义

JSON Web Token,一种开放标准,用于在各方之间安全地传输信息,通常用于身份认证和信息交换。

结构

三部分

复制代码
header.payload.signature
  • header 包含令牌类型和使用的签名算法
  • payload 包含声明,即要传输的数据,比如用户ID、角色、过期时间等
  • signature 用于验证消息未被篡改,由 Header+Payload+密钥 通过指定算法生成

用途

  • 用户登录后,服务器生成一个 JWT 返回给客户端。
  • 客户端在后续请求的 Authorization 头中携带该JWT。
  • 服务器验证JWT的签名和有效期,从而确认用户身份,无需每次都查询数据库。

优点

  • 无状态(stateless) 服务端不需要保存会话信息
  • 可跨域使用
  • 自包含 Payload 中可携带必要信息

拦截器

定义

拦截器是一种中间件机制,用于在请求到达目标处理函数(如控制器方法) 之前或之后执行通用逻辑。

常见功能

  • 身份验证
  • 日志记录
  • 请求/响应修改
  • 性能监控
  • 错误统一处理

典型使用场景

JWT和拦截器的使用典型场景是:

  1. 用户登录后获得JWT;
  2. 后续每个请求都带上JWT
  3. 服务器的拦截器在请求进入业务逻辑前
    • 检验是否存在 JWT
    • 验证JWT的签名和有效期
    • 如果有效,解析用户信息并附加到请求上下文种
    • 如果无效,直接返回 401 错误,阻止请求继续
      业务代码无需重复处理认证逻辑。

操作步骤

添加依赖

maven的pom.xml中添加依赖,添加后出现标红,需要reload,刷新Maven

xml 复制代码
<!-- JWT 工具包 -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>
<!-- XML 解析器 (JDK 11+ 需要手动引入) -->
<dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
    <version>2.3.1</version>
</dependency>

创建 JWT 工具类

后端工具类(一般是utils目录),创建 JwtUtils.java

分为几个步骤:

  1. 设置密钥
  2. 过期时间
  3. 生成 token
  4. 解析 token,获取 Claims
java 复制代码
package com.student.devflow.utils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;

public class JwtUtils {
    // 密钥 (真实项目中应该配置在 yml 里,这里为了简单写死)
    private static final String SECRET = "123456";
    // 过期时间 24小时
    private static final long EXPIRATION = 86400000;

    // 生成 Token
    public static String generateToken(Long userId, String username) {
        return Jwts.builder()
                .setSubject(username)
                .claim("userId", userId)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION))
                .signWith(SignatureAlgorithm.HS256, SECRET)
                .compact();
    }

    // 解析 Token 获取 Claims
    public static Claims getClaimsByToken(String token) {
        try {
            return Jwts.parser()
                    .setSigningKey(SECRET)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            return null; // 解析失败或过期
        }
    }
}

创建登录拦截器

要拦截所有的请求,检查Header中有没有token。

代码位置:

后端/config/xxx.java

java 复制代码
package com.student.devflow.config;

import com.student.devflow.utils.JwtUtils;
import io.jsonwebtoken.Claims;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

@Component
public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 放行 OPTIONS 请求 (跨域预检)
        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
            return true;
        }

        // 1. 从 Header 获取 Token
        String token = request.getHeader("Authorization");
        
        // 2. 校验 Token
        if (token != null && !token.isEmpty()) {
            Claims claims = JwtUtils.getClaimsByToken(token);
            if (claims != null) {
                // 登录成功,放行
                // 可以把 userId 存入 request,方便 Controller 使用
                request.setAttribute("userId", claims.get("userId"));
                return true;
            }
        }

        // 3. 失败,返回 401
        response.setStatus(401);
        response.getWriter().write("{\"code\": 401, \"msg\": \"No Permission\"}");
        return false;
    }
}
代码细节
  1. 判断条件
java 复制代码
token != null && !token.isEmpty()

一种防御性编程的经典写法,安全地判断一个字符串是否"真正有内容"。

token != null判断token是否为null。

如果前端根本没传 Authorization头,request.getHeader("Authorization") 会返回 null

如果直接对 null调用 isEmpty(),会抛出空指针异常。

!token.isEmpty()

在确认 token不是null的前提下,在判断它是否为空字符串。

有些客户端传了Header,但它是空的。

  1. Claims claims = JwtUtils.getClaimsByToken(token);
    是 JWT 的 payload 部分的Java对象表示,本质上是键值对,存储了签发Token时放入的所有信息。
    JWT 的结构是

    Header.PayloadSignature

其中 ````Payload```是JSON格式:

json 复制代码
{
  "sub": "alice",
  "userId": 1001,
  "iat": 1712345678,
  "exp": 1712432078
}

调用

复制代码
Claims claims = JwtUtils.getClaimsByToken(token);

时,JwtUtils内部使用Jwts.parser().parseClaimsJws(token).getBody(),会验证签名+检查过期

如果成功,则会把 payload 解析成一个 Claims对象。

Claims继承自Map<String, Object>,可以像操作Map一样操作它。比如调用get方法。

补充知识

中间件

  1. 在请求到达最终处理逻辑之前(或之后)执行的函数,用于处理通用任务,比如日志记录、身份验证、数据解析、错误处理等。
  2. 特点
    • 可以访问请求对象、响应对象,以及下一个中间件。
    • 可以修改请求或响应。
    • 可以决定是否将请求继续传递给下一个中间件或最终处理函数。
    • 可以提前终止请求(比如返回401未授权错误)
      在spring中,叫Filter或Interceptor

目标处理函数

  1. 通常指真正处理某个 HTTP 请求业务逻辑的函数,也就是请求流程的终点。
  2. 比如Express的路由处理函数,Spring Controller方法,NestJS控制器的方法等。

控制器方法

  1. MVC架构或现代Web框架中,控制器是专门负责处理HTTP请求的类,控制器方法是类中的具体方法,每个方法对应一个路由(URL+HTTP方法)。
  2. 作用:1. 接收请求参数;2. 调用业务逻辑;3. 返回响应。

典型的 HTTP 请求处理顺序

复制代码
[客户端请求]
     ↓
[全局中间件] → [路由中间件] → [控制器方法(即目标处理函数)]
     ↓
[响应返回]

签名密钥

用于JWT的签名与验签,核心目的是确保JWT的完整性和真实性。

包括令牌未被篡改、令牌确实由可信服务器签发。

如果没有签名或密钥泄露,攻击者可以伪造任意用户身份,如修改userId为管理员ID,服务器无法判断令牌是否可信。

密钥在JWT生命周期中的使用步骤
  1. 生成JWT时,用密钥进行签名。

  2. 服务器将 Header 和 Payload 拼接成字符串(Base64Url 编码后)。使用 HMAC-SHA256 算法 + SECRET 密钥 对这个字符串进行加密,生成 Signature。(只有知道SECRET的人,才能生成合法的签名)

  3. 验证JWT时,用密钥重新计算并比对签名。

  4. 服务器收到JWT,从token中提取headerpayload,用同样的算法和相同的SECRET,重新计算签名,

    expectedSignature = HMAC-SHA256(a + "." + b, SECRET)

  5. 将计算出的expectedSignature与token中的原始签名进行比对,如果一致,则令牌合法,未被篡改,如果不一致,则是令牌被伪造或密钥错误,抛出SignatureException

图解流程
复制代码
[服务器生成 JWT]
       ↓
Header + Payload + 【SECRET】 → 计算 Signature → 组成 token
       ↓
[客户端保存 token,每次请求携带]
       ↓
[服务器收到 token]
       ↓
用【SECRET】重新计算 Signature
       ↓
比对计算值 vs token 中的 Signature
       ↓
一致? → 信任数据(如 userId) → 允许访问
不一致? → 拒绝请求(401 Unauthorized)
相关推荐
带刺的坐椅2 小时前
Liquor(Java 脚本)替代 Groovy 作脚本引擎的可行性分析
java·groovy·liquor
武子康2 小时前
Java-203 RabbitMQ 生产者/消费者工作流程拆解:Connection/Channel、默认交换器、ACK
java·分布式·消息队列·rabbitmq·erlang·ruby·java-rabbitmq
Coder_Boy_2 小时前
前端和后端软件系统联调经典问题汇总
java·前端·驱动开发·微服务·状态模式
雨中飘荡的记忆2 小时前
Retrofit:优雅的JAVA网络请求框架实战
java
Thexhy2 小时前
基础篇:Redis核心命令及用法
java·linux·redis
狂奔小菜鸡2 小时前
Day33 | Java中的Optional
java·后端·java ee
啃火龙果的兔子2 小时前
IntelliJ IDEA社区版下载安装
java·ide·intellij-idea
ckm紫韵2 小时前
Cursor 与 IDEA 互相跳转教程
java·ide·intellij-idea·cursor·ai工具
渡过晚枫2 小时前
[蓝桥杯/java/算法]攻击次数
java·算法·蓝桥杯