JWT 登录校验机制:5 大核心类打造 Spring Boot 接口安全屏障

在前后端分离的项目开发中,接口的登录校验是保障系统安全的第一道防线。传统的 Session-Cookie 校验方式受限于跨域问题,难以适配分布式架构,而JWT(JSON Web Token) 凭借无状态、跨域友好的特性,成为前后端分离项目的主流登录校验方案。

在实际项目中,我们通过5 个核心类的紧密配合,基于 JWT 封装了一套完整的登录校验机制,实现了 "Token 生成 - 请求拦截 - Token 校验 - 用户信息传递" 的全流程自动化处理,既保证了接口安全,又让用户信息传递更优雅。本文将从核心类职责、代码实现、执行流程三个维度,完整解析这套 JWT 登录校验方案的设计与实现。

一、JWT 登录校验核心思想:无状态的身份认证

在讲解具体实现前,先理解 JWT 登录校验的核心思想,这是整个方案的设计基础:

  1. 无状态:服务端不存储任何用户登录信息,所有身份信息都加密存储在 JWT Token 中,服务端只需通过秘钥校验 Token 的合法性,无需依赖数据库 / 缓存查询,适配分布式架构;
  2. 一次登录,全程有效:用户登录成功后,服务端生成包含用户核心信息(如用户 ID)的 Token 返回给前端,后续前端请求需在请求头中携带该 Token,服务端通过校验 Token 完成身份认证;
  3. 全程自动化:通过 Spring MVC 拦截器实现请求的自动拦截与 Token 校验,校验通过后自动将用户信息存入 ThreadLocal,后续业务层可无感获取,无需手动传参。

整个方案的核心是5 个类各司其职、协同工作,分别承担 "配置读取、Token 处理、请求拦截、注册配置、信息传递" 的职责,形成一套完整的接口安全防护体系。

二、五大核心类:各司其职,构筑 JWT 校验链路

以下按 "执行流程顺序" 讲解每个核心类的具体职责、核心代码和设计细节,所有代码均为项目实战可直接使用的版本,贴合企业级开发规范。

1. JwtProperties:配置读取类 ------ 统一管理 JWT 配置项

核心职责 :专门读取application.yml中的 JWT 相关配置(如秘钥、过期时间、Token 请求头名称),通过 Spring Boot 的配置绑定功能,将配置文件中的数据封装为 Java 对象,供其他类注入使用。设计优势:将配置项与业务代码解耦,后续修改 JWT 配置(如更换秘钥、调整过期时间),只需修改配置文件,无需改动任何业务代码,符合 "配置与代码分离" 的开发原则。

核心代码实现
java 复制代码
package com.sky.properties;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * JWT配置属性类,绑定application.yml中的JWT配置
 */
@Data // Lombok注解,自动生成get/set方法
@Component // 交给Spring容器管理
@ConfigurationProperties(prefix = "sky.jwt") // 绑定配置文件中前缀为sky.jwt的配置
public class JwtProperties {
    /**
     * 管理端用户生成Token的秘钥
     */
    private String adminSecretKey;
    /**
     * 管理端用户Token的过期时间(毫秒)
     */
    private long adminTtl;
    /**
     * 管理端用户Token在请求头中的名称
     */
    private String adminTokenName;
}
对应 application.yml 配置
复制代码
# JWT配置
sky:
  jwt:
    admin-secret-key: sky-admin-2024 # 管理端秘钥,建议生产环境使用复杂随机字符串
    admin-ttl: 7200000 # Token过期时间,2小时(毫秒)
    admin-token-name: token # 前端请求头中携带Token的名称

2. JwtUtil:工具类 ------Token 的 "生成器" 与 "解析器"

核心职责 :JWT 操作的通用工具类,封装 Token 的生成解析 两个核心方法,提供给登录接口和拦截器使用,是 JWT 校验的基础工具。设计优势 :工具类方法设计为static,无需实例化即可调用;核心依赖 JJWT 框架实现 JWT 的加密与解密,屏蔽底层复杂的加密逻辑,对外提供简洁的调用接口。

核心代码实现
java 复制代码
package com.sky.utils;

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

/**
 * JWT工具类,生成和解析Token
 */
public class JwtUtil {

    /**
     * 生成JWT Token
     * @param secretKey 加密秘钥
     * @param ttlMillis Token过期时间(毫秒)
     * @param claims 需存入Token的自定义数据(如用户ID)
     * @return 加密后的Token字符串
     */
    public static String createJWT(String secretKey, long ttlMillis, Map<String, Object> claims) {
        // 1. 设置Token过期时间
        long expMillis = System.currentTimeMillis() + ttlMillis;
        Date expDate = new Date(expMillis);

        // 2. 构建JWT Token
        return Jwts.builder()
                .setClaims(claims) // 存入自定义数据
                .setExpiration(expDate) // 设置过期时间
                .signWith(SignatureAlgorithm.HS256, secretKey) // 指定加密算法和秘钥
                .compact(); // 生成并返回Token字符串
    }

    /**
     * 解析JWT Token,获取其中的自定义数据
     * @param secretKey 加密秘钥(需与生成时一致)
     * @param token 待解析的Token字符串
     * @return 包含自定义数据的Claims对象
     */
    public static Claims parseJWT(String secretKey, String token) {
        // 解析Token并获取Claims(存储自定义数据的容器)
        return Jwts.parser()
                .setSigningKey(secretKey) // 设置解密秘钥
                .parseClaimsJws(token) // 解析Token
                .getBody(); // 获取自定义数据
    }
}
关键说明
  • Claims:JJWT 框架提供的容器,用于存储需要加密到 Token 中的自定义数据(如用户 ID),解析 Token 后可直接从 Claims 中获取;
  • 加密算法:这里使用 HS256 对称加密算法,生成和解析时使用相同的秘钥,保证 Token 不被篡改;
  • 过期时间:生成 Token 时指定过期时间,解析时若 Token 已过期,会直接抛出异常,实现 Token 的自动失效。

3. JwtTokenAdminInterceptor:拦截器 ------ 接口的 "安保人员"

核心职责 :整个 JWT 登录校验的核心类 ,相当于系统的 "安保人员"。实现 Spring MVC 的HandlerInterceptor接口,在请求到达 Controller 前拦截请求,完成 Token 的提取、校验,并将用户信息存入 ThreadLocal。核心时机 :重写preHandle方法,在Controller 方法执行前执行拦截逻辑,校验不通过则直接拦截请求,避免非法请求进入业务层。

核心代码实现
java 复制代码
package com.sky.interceptor;

import com.sky.constant.JwtClaimsConstant;
import com.sky.properties.JwtProperties;
import com.sky.utils.BaseContext;
import com.sky.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 管理端JWT Token拦截器,校验请求的Token合法性
 */
@Component // 交给Spring容器管理,方便后续注册
@Slf4j
public class JwtTokenAdminInterceptor implements HandlerInterceptor {

    @Autowired
    private JwtProperties jwtProperties; // 注入JWT配置

    /**
     * 前置拦截:请求到达Controller前执行
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("开始拦截管理端请求,校验JWT Token...");
        // 1. 从请求头中提取Token(配置文件中指定的头名称,如token)
        String token = request.getHeader(jwtProperties.getAdminTokenName());

        try {
            // 2. 调用JwtUtil解析Token,校验合法性(秘钥不匹配/Token过期都会抛出异常)
            Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);
            // 3. 从Claims中获取用户ID(登录时存入的核心信息)
            Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
            log.info("当前登录用户ID:{}", empId);
            // 4. 将用户ID存入ThreadLocal,供后续业务层使用(核心!)
            BaseContext.setCurrentId(empId);
            // 5. 校验通过,放行请求
            return true;
        } catch (Exception e) {
            // 6. 校验失败(Token非法/过期/空),返回401未授权状态码,拦截请求
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 401状态码
            log.error("JWT Token校验失败:{}", e.getMessage());
            return false;
        }
    }
}
关键细节
  • 异常捕获:解析 Token 时的所有异常(如 Token 为空、秘钥不匹配、Token 过期、格式错误)都统一捕获,避免程序崩溃,同时返回 401 状态码告知前端;
  • 用户 ID 传递 :校验通过后,将用户 ID 存入BaseContext(基于 ThreadLocal 的上下文工具类),实现同一请求线程内的用户信息无感传递,后续 Service 层可直接获取;
  • 只拦截管理端 :该拦截器专为管理端接口设计,后续可通过相同方式实现用户端 JWT 拦截器(JwtTokenUserInterceptor)。

4. WebMvcConfiguration:注册配置类 ------ 让拦截器 "生效"

核心职责 :Spring MVC 的全局配置类,完成两件核心事:① 将 JWT 拦截器注册到 Spring MVC 的拦截器链中;② 配置拦截规则(拦截哪些路径、排除哪些路径)。实现方式 :继承WebMvcConfigurationSupport,重写addInterceptors方法,通过InterceptorRegistry完成拦截器的注册和规则配置。

核心代码实现
java 复制代码
package com.sky.config;

import com.sky.interceptor.JwtTokenAdminInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

/**
 * Spring MVC全局配置类,注册拦截器、消息转换器等
 */
@Configuration // 标记为配置类,交给Spring容器管理
public class WebMvcConfiguration extends WebMvcConfigurationSupport {

    @Autowired
    private JwtTokenAdminInterceptor jwtTokenAdminInterceptor; // 注入JWT拦截器

    /**
     * 注册拦截器,配置拦截规则
     */
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        log.info("开始注册JWT拦截器及拦截规则...");
        registry.addInterceptor(jwtTokenAdminInterceptor)
                .addPathPatterns("/admin/**") // 拦截所有管理端接口(/admin/开头的所有路径)
                .excludePathPatterns("/admin/employee/login"); // 排除登录接口(无需校验Token)
    }
}
关键拦截规则说明
  • addPathPatterns("/admin/**") :拦截所有以/admin/开头的请求,即所有管理端接口,保证管理端接口的安全性;
  • excludePathPatterns("/admin/employee/login"):排除登录接口,因为登录接口是获取 Token 的入口,此时用户尚未登录,无 Token 可校验,必须放行;
  • 可扩展 :若有其他无需校验的接口(如验证码接口),直接添加到excludePathPatterns中即可。

5. BaseContext:上下文工具类 ------ 用户 ID 的 "线程传递容器"

核心职责 :基于ThreadLocal实现同一请求线程内的用户 ID 传递,是 JWT 拦截器与业务层之间的 "桥梁"。拦截器将用户 ID 存入其中,Service/Mapper 层可直接获取,无需通过方法参数层层传递。设计基础:Tomcat 为每个 HTTP 请求分配独立线程,请求的整个处理流程(拦截器→Controller→Service→Mapper)都在该线程中执行,ThreadLocal 保证了线程内数据共享、线程间数据隔离。

核心代码实现
java 复制代码
package com.sky.utils;

/**
 * 基于ThreadLocal的上下文工具类,传递当前登录用户ID
 */
public class BaseContext {
    // 定义ThreadLocal变量,存储Long类型的用户ID
    private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();

    /**
     * 存入用户ID
     * @param id 当前登录用户ID
     */
    public static void setCurrentId(Long id) {
        threadLocal.set(id);
    }

    /**
     * 获取用户ID
     * @return 当前登录用户ID
     */
    public static Long getCurrentId() {
        return threadLocal.get();
    }

    /**
     * 移除用户ID,避免内存泄漏
     */
    public static void removeCurrentId() {
        threadLocal.remove();
    }
}
业务层使用示例

在 Service 层中,无需任何参数传递,直接调用BaseContext.getCurrentId()即可获取当前登录用户 ID,用于记录操作人(如创建人、修改人),完美配合公共字段自动填充功能:

java 复制代码
// 新增员工时,获取当前登录管理员ID,作为创建人
employee.setCreateUser(BaseContext.getCurrentId());
// 修改菜品时,获取当前登录管理员ID,作为修改人
dish.setUpdateUser(BaseContext.getCurrentId());

三、完整执行流程:从请求到来至业务层执行

当五大核心类配置完成后,整个 JWT 登录校验流程将自动化执行 ,对前端和业务层开发者完全透明。以下以管理端新增员工为例,讲解从请求到来至业务层执行的完整流程:

前提:用户已完成登录,前端持有有效 Token

用户在管理端登录时,后端登录接口(/admin/employee/login)校验账号密码成功后,通过JwtUtil.createJWT()生成包含用户 ID 的 Token,返回给前端,前端将 Token 存储在本地(如 localStorage),后续所有请求都在请求头中携带该 Token。

正式执行流程(共 6 步)

  1. 请求发送 :前端发送新增员工请求(/admin/employee/save),在请求头中携带 Token(如token: eyJhbGciOiJIUzI1NiJ9...);
  2. 规则匹配 :Spring MVC 通过WebMvcConfiguration中的配置,发现请求路径/admin/employee/save匹配/admin/**,触发 JWT 拦截器;
  3. Token 提取JwtTokenAdminInterceptor拦截请求,从请求头中提取 Token(根据JwtProperties配置的adminTokenName);
  4. Token 校验 :拦截器调用JwtUtil.parseJWT(),使用JwtProperties中的秘钥解析 Token,校验其合法性(秘钥是否匹配、是否过期);
  5. 信息传递 :校验通过后,从解析后的 Claims 中获取用户 ID,调用BaseContext.setCurrentId()将 ID 存入 ThreadLocal,放行请求;
  6. 业务执行 :请求进入 Controller→Service 层,业务层通过BaseContext.getCurrentId()获取当前登录用户 ID,完成业务逻辑执行(如记录创建人)。

异常情况处理

若前端请求未携带 Token、Token 非法、Token 过期,拦截器会直接捕获异常,设置响应状态码为 401 并拦截请求,请求无法进入业务层,保证接口安全。

四、登录接口实现:Token 的生成入口

为了让整个方案更完整,这里补充管理端登录接口的核心代码,这是 Token 的生成入口,与 JWT 校验机制形成完整的闭环:

1. 登录请求 DTO(接收前端参数)

java 复制代码
package com.sky.dto;

import lombok.Data;

@Data
public class EmployeeLoginDTO {
    private String username; // 用户名
    private String password; // 密码
}

2. 登录接口 Controller 层

java 复制代码
package com.sky.controller.admin;

import com.sky.dto.EmployeeLoginDTO;
import com.sky.properties.JwtProperties;
import com.sky.utils.JwtUtil;
import com.sky.vo.EmployeeLoginVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/admin/employee")
public class EmployeeController {

    @Autowired
    private EmployeeService employeeService;
    @Autowired
    private JwtProperties jwtProperties;

    /**
     * 员工登录
     */
    @PostMapping("/login")
    public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO) {
        // 1. 业务层校验账号密码,返回登录成功的员工信息
        Employee employee = employeeService.login(employeeLoginDTO);

        // 2. 生成JWT Token,存入用户ID
        Map<String, Object> claims = new HashMap<>();
        claims.put(JwtClaimsConstant.EMP_ID, employee.getId());
        String token = JwtUtil.createJWT(
                jwtProperties.getAdminSecretKey(),
                jwtProperties.getAdminTtl(),
                claims
        );

        // 3. 封装返回结果,将Token和用户信息返回给前端
        EmployeeLoginVO vo = new EmployeeLoginVO();
        vo.setId(employee.getId());
        vo.setName(employee.getName());
        vo.setToken(token);
        return Result.success(vo);
    }
}

五、方案优势:为什么这套 JWT 校验机制适用于企业级开发?

这套基于 5 大核心类的 JWT 登录校验机制,并非简单的 JWT 工具类调用,而是结合 Spring MVC、ThreadLocal 的企业级最佳实践,相比简单的 JWT 实现,具有以下核心优势:

  1. 配置与代码解耦 :通过JwtProperties统一管理 JWT 配置,修改配置无需改动业务代码,符合开闭原则;
  2. 接口安全全覆盖:通过拦截器实现管理端接口的全量拦截,仅排除登录接口,从入口处保障接口安全;
  3. 用户信息无感传递 :基于 ThreadLocal 的BaseContext,实现用户 ID 的跨层传递,避免方法参数层层传递的繁琐,让代码更优雅;
  4. 无状态设计,适配分布式:服务端不存储任何登录信息,Token 自包含所有身份信息,可直接部署在分布式集群中,无需考虑 Session 共享问题;
  5. 可扩展性强 :可快速扩展用户端 JWT 校验(新增JwtTokenUserInterceptor,配置/user/**拦截规则),与管理端校验逻辑互不干扰;
  6. 异常友好:统一的异常捕获和 401 状态码返回,前端可根据状态码统一处理未登录 / Token 过期场景,提升用户体验;
  7. 代码复用性高JwtUtil作为通用工具类,可在整个项目中复用,登录接口、拦截器直接调用,无需重复开发。

六、生产环境优化建议

以上实现为项目基础版 JWT 校验机制,在生产环境中,可根据实际需求进行以下优化,让方案更安全、更健壮:

  1. 使用非对称加密算法:将 HS256 对称加密替换为 RSA 非对称加密,生成 Token 用私钥,解析 Token 用公钥,提升 Token 的安全性,防止秘钥泄露;
  2. 增加 Token 刷新机制:为 Token 添加刷新令牌(Refresh Token),当 Access Token 过期时,前端可通过 Refresh Token 获取新的 Access Token,避免用户频繁登录;
  3. Token 黑名单机制:针对需要强制下线的用户(如修改密码、账号冻结),添加 Redis 实现的 Token 黑名单,拦截已失效但未过期的 Token;
  4. 请求头防篡改:结合请求头签名,防止 Token 被篡改或伪造;
  5. 限制 Token 请求频率:添加接口限流,防止针对 JWT 接口的暴力破解攻击;
  6. 完善日志记录:在拦截器中增加详细的日志记录(如请求 IP、Token 校验结果、用户 ID),便于生产环境问题排查;
  7. 秘钥安全管理:生产环境的 JWT 秘钥不直接写在配置文件中,通过配置中心(如 Nacos、Apollo)或环境变量注入,防止秘钥泄露。

七、总结

本文讲解的 JWT 登录校验机制,是Spring Boot + JWT + AOP(拦截器) 在企业级项目中的经典落地实践,核心是通过 5 个核心类的协同工作,实现了从 Token 生成、请求拦截、Token 校验到用户信息传递的全流程自动化。

这套方案的核心价值不仅在于保障了接口安全,更在于让安全校验与业务逻辑完全解耦------ 开发者无需在业务层编写任何校验代码,只需专注于业务逻辑实现;同时通过 ThreadLocal 实现用户信息的无感传递,让代码更简洁、更优雅。

在实际项目中,这套方案可直接复用,并根据业务需求扩展用户端校验、Token 刷新、黑名单等功能。掌握这套方案,不仅能解决前后端分离项目的登录校验问题,更能理解 Spring MVC 拦截器、配置绑定、ThreadLocal 等核心技术的综合应用,提升企业级项目的开发能力。

JWT 的核心是无状态的身份认证,而优秀的项目实现,是让这份无状态的安全,与 Spring 生态完美融合,形成一套可扩展、可维护、高安全的完整解决方案。

相关推荐
C澒3 小时前
系统初始化成功率下降排查实践
前端·安全·运维开发
To Be Clean Coder3 小时前
【Spring源码】createBean如何寻找构造器(二)——单参数构造器的场景
java·后端·spring
只是懒得想了3 小时前
C++实现密码破解工具:从MD5暴力破解到现代哈希安全实践
c++·算法·安全·哈希算法
云边云科技_云网融合3 小时前
AIoT智能物联网平台:架构解析与边缘应用新图景
大数据·网络·人工智能·安全
你才是臭弟弟3 小时前
SpringBoot 集成MinIo(根据上传文件.后缀自动归类)
java·spring boot·后端
C澒3 小时前
面单打印服务的监控检查事项
前端·后端·安全·运维开发·交通物流
运维有小邓@3 小时前
生物制药企业 AD 域管理破局:合规 · 效率 · 安全三维解决方案
人工智能·安全
大力财经3 小时前
喜茶2025年批量重装130多家门店
安全
青岛前景互联信息技术有限公司3 小时前
政策支撑:应急部推动化工园区安全风险智能化管控平台有效应用!
大数据·人工智能·安全
珑哥说自养号采购3 小时前
TEMU采购下单,卖家如何搭建安全的环境?
安全