Spring Boot 整合 JWT:从入门到源码原理深度解析

Spring Boot 整合 JWT:从入门到源码原理深度解析

    • 前言
    • [一、JWT 核心认知](#一、JWT 核心认知)
    • [二、实战:Spring Boot 整合 JWT](#二、实战:Spring Boot 整合 JWT)
      • [2.1 引入依赖](#2.1 引入依赖)
      • [2.2 配置文件](#2.2 配置文件)
      • [2.3 JWT 工具类封装](#2.3 JWT 工具类封装)
      • [2.4 统一响应结果封装](#2.4 统一响应结果封装)
      • [2.5 登录接口------生成 Token](#2.5 登录接口——生成 Token)
      • [2.6 拦截器------统一 Token 校验](#2.6 拦截器——统一 Token 校验)
        • [2.6.1 创建 JWT 拦截器](#2.6.1 创建 JWT 拦截器)
        • [2.6.2 注册拦截器](#2.6.2 注册拦截器)
    • [三、进阶:Spring Security + JWT](#三、进阶:Spring Security + JWT)
      • [3.1 JWT 认证过滤器](#3.1 JWT 认证过滤器)
      • [3.2 安全配置](#3.2 安全配置)
    • 四、源码原理深度解析
      • [4.1 JJWT 生成 Token 的底层流程](#4.1 JJWT 生成 Token 的底层流程)
      • [4.2 拦截器的执行原理](#4.2 拦截器的执行原理)
      • [4.3 关于 Token 存储的思考](#4.3 关于 Token 存储的思考)
    • 五、最佳实践总结
      • [5.1 核心要点回顾](#5.1 核心要点回顾)
      • [5.2 安全注意事项](#5.2 安全注意事项)
      • [5.3 原理一句话总结](#5.3 原理一句话总结)

前言

在前后端分离和微服务架构盛行的今天,传统的 Session 认证方式正面临着越来越多的挑战------Session 依赖服务器内存存储,分布式部署时存在会话共享问题,且跨域场景下 Cookie 传递受限。

JWT(JSON Web Token)作为一种无状态、自包含的身份认证方案,正逐渐成为业界主流。它让服务端无需存储会话信息,每次请求只需验证令牌本身即可完成身份认证,天然适配分布式和前后端分离架构。

本文将从代码实践到源码原理,全面讲解 Spring Boot 整合 JWT 的实现方式。


一、JWT 核心认知

1.1 什么是 JWT

JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在各方之间以 JSON 对象的形式安全地传输信息。它通常用于用户身份认证、信息交换、单点登录(SSO)和 API 接口鉴权。

核心优势

优势 说明
无状态(Stateless) 服务端无需存储会话信息,减轻服务器压力
自包含(Self-contained) 所有用户信息都包含在令牌中
跨域支持 天然支持前后端分离、微服务架构
可扩展性好 支持自定义声明(Claims)

1.2 JWT 的结构

一个 JWT 令牌由三部分组成,用 . 分隔:xxxxx.yyyyy.zzzzz

(1)Header(头部)

包含令牌类型和签名算法:

json 复制代码
{
  "alg": "HS256",
  "typ": "JWT"
}
  • alg:签名算法,如 HS256(HMAC SHA-256)、RS256(RSA)
  • typ:令牌类型,固定为 JWT
(2)Payload(负载)

包含"声明(Claims)"------关于用户和附加信息的 JSON 数据。分为三类:

类型 说明 示例
Registered Claims 预定义标准字段(非强制) exp(过期时间)、iss(发行人)、sub(主题)
Public Claims 公共字段,建议使用命名空间 https://example.com/role
Private Claims 自定义私有字段 userIdusernamerole

⚠️ 重要提醒 :Header 和 Payload 仅经过 Base64 编码,并未加密 ,任何人都可以解码查看内容。千万不要在 Payload 中存放密码等敏感信息

(3)Signature(签名)

用于验证令牌完整性和防止篡改。生成方式:

复制代码
HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secretKey
)

1.3 JWT 认证流程

整个认证流程分为四步:

  1. 登录认证:客户端提交用户名/密码,服务器验证通过后生成 JWT Token 返回
  2. 存储令牌:客户端接收 Token 后存储在本地(localStorage、Cookie 等)
  3. 携带令牌 :后续所有请求都在请求头(Authorization: Bearer <token>)中携带 Token
  4. 验证令牌:服务器拦截请求,解析并验证 Token 的有效性(签名、过期时间等)

二、实战:Spring Boot 整合 JWT

2.1 引入依赖

使用 JJWT(Java JWT)库来简化 JWT 的生成、解析和验证操作。推荐使用 0.11.5 版本,兼容 Spring Boot 2.x/3.x:

xml 复制代码
<!-- JJWT 核心 API -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.5</version>
</dependency>
<!-- JJWT 运行时实现 -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>
<!-- JJWT Jackson 序列化支持 -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>

2.2 配置文件

application.yml 中配置 JWT 相关参数:

yaml 复制代码
jwt:
  secret: your-secret-key-please-change-in-production
  expire: 7200000  # 2小时,单位毫秒

2.3 JWT 工具类封装

工具类封装了生成 Token、解析 Token、验证 Token 三大核心方法:

java 复制代码
package com.example.demo.utils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Slf4j
@Component
public class JwtUtils {

    @Value("${jwt.secret}")
    private String secret;

    @Value("${jwt.expire}")
    private Long expire;

    /**
     * 获取签名密钥
     */
    private SecretKey getSecretKey() {
        return Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
    }

    /**
     * 生成 JWT Token
     */
    public String generateToken(Long userId, String username, String role) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("userId", userId);
        claims.put("username", username);
        claims.put("role", role);

        return Jwts.builder()
                .setClaims(claims)                    // 载荷信息
                .setIssuedAt(new Date())              // 签发时间
                .setExpiration(new Date(System.currentTimeMillis() + expire))  // 过期时间
                .signWith(getSecretKey(), SignatureAlgorithm.HS256)  // 签名算法
                .compact();                           // 拼接生成最终令牌
    }

    /**
     * 解析 JWT Token,获取 Claims
     */
    public Claims parseToken(String token) {
        return Jwts.parserBuilder()
                .setSigningKey(getSecretKey())
                .build()
                .parseClaimsJws(token)
                .getBody();  // 获取 Payload 部分
    }

    /**
     * 验证 Token 是否有效
     */
    public boolean validateToken(String token) {
        try {
            Claims claims = parseToken(token);
            // 检查是否过期
            Date expiration = claims.getExpiration();
            if (expiration != null && expiration.before(new Date())) {
                log.warn("Token 已过期");
                return false;
            }
            return true;
        } catch (Exception e) {
            log.error("Token 验证失败:{}", e.getMessage());
            return false;
        }
    }

    /**
     * 从 Token 中获取用户 ID
     */
    public Long getUserIdFromToken(String token) {
        Claims claims = parseToken(token);
        return claims.get("userId", Long.class);
    }

    /**
     * 从 Token 中获取用户名
     */
    public String getUsernameFromToken(String token) {
        Claims claims = parseToken(token);
        return claims.get("username", String.class);
    }
}

代码要点说明

  • Keys.hmacShaKeyFor():将字符串密钥转换为 SecretKey 对象,这是 JJWT 0.11.x 版本的标准做法
  • setClaims():设置自定义载荷内容
  • setExpiration():设置过期时间,单位毫秒
  • signWith():指定签名算法和密钥
  • parseClaimsJws():解析签名后的 JWT(带签名的完整令牌)
  • compact():将各部分拼接成最终的令牌字符串

2.4 统一响应结果封装

java 复制代码
package com.example.demo.common;

import lombok.Data;

@Data
public class Result<T> {
    private int code;
    private String message;
    private T data;

    public static <T> Result<T> success(T data) {
        Result<T> result = new Result<>();
        result.setCode(200);
        result.setMessage("success");
        result.setData(data);
        return result;
    }

    public static <T> Result<T> error(int code, String message) {
        Result<T> result = new Result<>();
        result.setCode(code);
        result.setMessage(message);
        return result;
    }
}

2.5 登录接口------生成 Token

java 复制代码
package com.example.demo.controller;

import com.example.demo.common.Result;
import com.example.demo.utils.JwtUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class LoginController {

    @Autowired
    private JwtUtils jwtUtils;

    @PostMapping("/login")
    public Result<String> login(@RequestParam String username,
                                 @RequestParam String password) {
        // 实际项目中这里需要查询数据库验证用户名和密码
        // 此处仅做演示,假设用户名为 admin,密码为 123456
        if ("admin".equals(username) && "123456".equals(password)) {
            // 验证通过,生成 Token
            // 实际项目中 userId 和 role 应从数据库查询
            String token = jwtUtils.generateToken(1L, username, "ADMIN");
            return Result.success(token);
        }
        return Result.error(401, "用户名或密码错误");
    }
}

2.6 拦截器------统一 Token 校验

拦截器的位置:位于过滤器和控制器之间,是 Spring MVC 框架的一部分。

用一句话比喻:

  • 过滤器(Filter) :像大楼的保安,检查每个进入大楼的人
  • 拦截器(Interceptor) :像公司前台,专门处理公司内部事务
  • 控制器(Controller) :像各个部门,处理具体的业务请求
2.6.1 创建 JWT 拦截器
java 复制代码
package com.example.demo.interceptor;

import com.example.demo.utils.JwtUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

@Slf4j
@Component
public class JwtInterceptor implements HandlerInterceptor {

    @Autowired
    private JwtUtils jwtUtils;

    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {
        // 步骤1:获取请求 URL
        String requestURL = request.getRequestURL().toString();
        log.info("拦截到请求: {}", requestURL);

        // 步骤2:判断是否为登录请求,登录请求放行
        if (requestURL.contains("/login")) {
            log.info("登录请求,直接放行");
            return true;
        }

        // 步骤3:从请求头中获取 Token
        String token = request.getHeader("Authorization");
        log.info("获取到的 Token: {}", token);

        // 步骤4:检查 Token 是否存在
        if (token == null || token.isEmpty()) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write("{\"code\":401,\"message\":\"未提供 Token\"}");
            return false;
        }

        // 步骤5:验证 Token
        // 如果 Token 以 "Bearer " 开头,需要去掉前缀
        if (token.startsWith("Bearer ")) {
            token = token.substring(7);
        }

        if (!jwtUtils.validateToken(token)) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write("{\"code\":401,\"message\":\"Token 无效或已过期\"}");
            return false;
        }

        // Token 验证通过,可以将用户信息存入 ThreadLocal 供后续使用
        // 例如:UserContext.setCurrentUser(jwtUtils.getUserIdFromToken(token));

        log.info("Token 验证通过,放行请求");
        return true;
    }
}

拦截器执行流程

复制代码
客户端请求 → 过滤器(Filter) → 拦截器(Interceptor) → 控制器(Controller)
                                      ↑
                                 preHandle() 执行 JWT 校验
2.6.2 注册拦截器

注意 :必须通过配置类注册拦截器,不能直接 new 对象,否则 Spring 无法管理依赖注入。

java 复制代码
package com.example.demo.config;

import com.example.demo.interceptor.JwtInterceptor;
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.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private JwtInterceptor jwtInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(jwtInterceptor)
                .addPathPatterns("/**")           // 拦截所有请求
                .excludePathPatterns("/login");   // 排除登录接口
    }
}

三、进阶:Spring Security + JWT

如果项目中使用了 Spring Security,可以在其过滤器链中集成 JWT。

3.1 JWT 认证过滤器

java 复制代码
package com.example.demo.filter;

import com.example.demo.utils.JwtUtils;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Autowired
    private JwtUtils jwtUtils;

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain)
            throws ServletException, IOException {
        // 获取 Token
        String token = request.getHeader("Authorization");
        if (token != null && token.startsWith("Bearer ")) {
            token = token.substring(7);
        }

        // 验证 Token
        if (token != null && jwtUtils.validateToken(token)) {
            String username = jwtUtils.getUsernameFromToken(token);
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);

            // 将用户信息存入 SecurityContext
            UsernamePasswordAuthenticationToken authentication =
                    new UsernamePasswordAuthenticationToken(
                            userDetails, null, userDetails.getAuthorities());
            authentication.setDetails(
                    new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }

        filterChain.doFilter(request, response);
    }
}

3.2 安全配置

java 复制代码
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Autowired
    private JwtAuthenticationFilter jwtAuthenticationFilter;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable())
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/login").permitAll()
                .anyRequest().authenticated()
            )
            .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }
}

四、源码原理深度解析

4.1 JJWT 生成 Token 的底层流程

当调用 Jwts.builder() 生成 Token 时,底层经历了以下步骤:

复制代码
Jwts.builder()
    .setClaims(claims)      → 将 Map 转换为 JSON,放入 Payload
    .setExpiration(date)    → 将过期时间加入 Payload(标准字段 exp)
    .signWith(key, HS256)   → 使用 HMAC-SHA256 对 Header+Payload 签名
    .compact()              → Base64Url(Header) + "." + Base64Url(Payload) + "." + Signature

签名原理

复制代码
Signature = HMACSHA256(
    base64UrlEncode(header) + "." + base64UrlEncode(payload),
    secretKey
)

服务器收到 Token 后,用同样的密钥重新计算签名,与 Token 中的签名比对------如果一致,说明 Token 未被篡改。

4.2 拦截器的执行原理

Spring MVC 中,拦截器的执行依赖于 DispatcherServletdoDispatch() 方法:

复制代码
doDispatch() {
    // 1. 获取 HandlerExecutionChain(包含拦截器链)
    HandlerExecutionChain mappedHandler = getHandler(request);
    
    // 2. 执行所有拦截器的 preHandle()
    if (!mappedHandler.applyPreHandle(request, response)) {
        return;  // 任一拦截器返回 false,请求被拦截
    }
    
    // 3. 执行 Controller 方法
    mv = ha.handle(request, response, mappedHandler.getHandler());
    
    // 4. 执行所有拦截器的 postHandle()
    mappedHandler.applyPostHandle(request, response, mv);
    
    // 5. 渲染视图后执行 afterCompletion()
}

JWT 拦截器正是在 applyPreHandle() 阶段完成 Token 校验 ,返回 false 则请求被拦截,不会到达 Controller。

4.3 关于 Token 存储的思考

JWT 本身是无状态的,但为了增强安全性,业界有一些实践:

方案 优点 缺点
纯 JWT(无存储) 完全无状态,扩展性好 Token 无法主动失效
JWT + Redis 黑名单 可主动失效 Token 引入额外存储
JWT + Refresh Token 用户体验好,可无感刷新 实现复杂

五、最佳实践总结

5.1 核心要点回顾

组件 作用
JwtUtils 封装 JWT 的生成、解析、验证三大核心方法
JwtInterceptor 实现 HandlerInterceptor,在 preHandle() 中统一校验 Token
WebConfig 注册拦截器,配置拦截路径和排除路径

5.2 安全注意事项

  1. 密钥管理:确保密钥的安全保管,避免泄露。生产环境应放在配置中心或环境变量中
  2. 过期时间设置:合理设置 JWT 的有效期,既要保证安全性,又要避免频繁重新登录
  3. 敏感信息保护绝对不要在 Payload 中存放密码等敏感信息
  4. 异常处理:妥善处理验证过程中可能出现的各种异常

5.3 原理一句话总结

JWT 本质上是一种将用户身份信息编码为三段式字符串(Header + Payload + Signature)的令牌规范 :Header 声明签名算法,Payload 承载用户信息,Signature 通过密钥对前两部分进行签名以确保完整性。Spring Boot 整合 JWT 的核心是通过拦截器在请求到达 Controller 之前统一校验 Token,验证通过后放行,否则拦截返回错误信息。

相关推荐
神经蛙199614 小时前
🌍 别再硬编码中文了!Python Web 项目国际化(i18n)完全指南
后端·python
二月龙14 小时前
Spring 事务失效的 8 种场景,很多老手依然频繁踩雷
后端
掘金酱14 小时前
「TRAE Work 实战帮」征文启动!你沉淀的经验,值得被看见!
前端·人工智能·后端
长大198815 小时前
MyBatis 常见性能陷阱:N+1 查询、一级缓存踩坑解决方案
后端
用户18615580086015 小时前
MinIO Java 对接试用:从连接、上传到下载的完整示例
后端
爱勇宝15 小时前
DeepSeek V4-Flash 更新:代码与 Agent 能力全面增强
前端·后端·deepseek
极客悟道15 小时前
SDKMAN vs jEnv vs JetTUI,JDK 版本管理到底选哪个
后端
长大198815 小时前
Java8 新特性到底要不要吃透?工作中高频使用的 5 个功能总结
后端
风中芦苇啊15 小时前
Java EasyExcel 导入通用工具类:自定义注解映射字段 + 反射机制
java·开发语言