Spring Boot + Token 拦截器实战代码(含配置、Controller、响应封装)
·
一、工程基础准备
1. 工程结构(标准 Maven 结构)
plaintext
src/main/java/com/example/token/
├── TokenApplication.java // 启动类
├── config/
│ └── WebMvcConfig.java // 拦截器配置类
├── interceptor/
│ └── AuthInterceptor.java // Token认证拦截器
├── util/
│ ├── JwtUtil.java // JWT Token工具类
│ └── UserContextHolder.java // ThreadLocal存储用户ID
├── controller/
│ └── UserController.java // 测试接口Controller
├── exception/
│ ├── GlobalExceptionHandler.java // 全局异常处理
│ └── TokenException.java // 自定义Token异常
└── vo/
└── ResultVo.java // 统一响应体
2. Maven 依赖(pom.xml)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version> <!-- 稳定版本,兼容JJWT -->
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>token-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>token-demo</name>
<description>Spring Boot + JWT Token 实战示例</description>
<dependencies>
<!-- Spring Boot Web核心依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- JJWT依赖(JWT Token生成/验证) -->
<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>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<!-- Lombok(简化代码,可选) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
二、核心代码实现
1. 启动类(TokenApplication.java)
package com.example.token;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class TokenApplication {
public static void main(String[] args) {
SpringApplication.run(TokenApplication.class, args);
}
}
2. 统一响应体(ResultVo.java)
package com.example.token.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 接口统一响应体
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ResultVo<T> {
// 响应码:200成功,401未授权,500服务器错误
private int code;
// 响应消息
private String msg;
// 响应数据
private T data;
// 快捷构造方法
public static <T> ResultVo<T> success(T data) {
return new ResultVo<>(200, "操作成功", data);
}
public static <T> ResultVo<T> fail(int code, String msg) {
return new ResultVo<>(code, msg, null);
}
}
3. 自定义 Token 异常(TokenException.java)
package com.example.token.exception;
/**
* 自定义Token相关异常
*/
public class TokenException extends RuntimeException {
private int code; // 异常对应的响应码
public TokenException(int code, String message) {
super(message);
this.code = code;
}
public int getCode() {
return code;
}
}
4. 全局异常处理(GlobalExceptionHandler.java)
package com.example.token.exception;
import com.example.token.vo.ResultVo;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.security.SignatureException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* 全局异常处理器,统一处理Token相关异常
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
// 处理自定义Token异常
@ExceptionHandler(TokenException.class)
public ResultVo<?> handleTokenException(TokenException e) {
return ResultVo.fail(e.getCode(), e.getMessage());
}
// 处理JWT过期异常
@ExceptionHandler(ExpiredJwtException.class)
public ResultVo<?> handleExpiredJwtException(ExpiredJwtException e) {
return ResultVo.fail(401, "Token已过期,请重新登录");
}
// 处理JWT签名异常(Token被篡改)
@ExceptionHandler(SignatureException.class)
public ResultVo<?> handleSignatureException(SignatureException e) {
return ResultVo.fail(401, "Token签名非法,可能已被篡改");
}
// 处理其他所有异常
@ExceptionHandler(Exception.class)
public ResultVo<?> handleException(Exception e) {
return ResultVo.fail(500, "服务器内部错误:" + e.getMessage());
}
}
5. ThreadLocal 工具类(UserContextHolder.java)
package com.example.token.util;
/**
* 存储当前请求的用户ID(ThreadLocal保证线程安全)
*/
public class UserContextHolder {
// ThreadLocal:每个线程独立存储,避免多线程数据混乱
private static final ThreadLocal<Long> USER_ID_HOLDER = new ThreadLocal<>();
// 设置当前线程的用户ID
public static void setUserId(Long userId) {
USER_ID_HOLDER.set(userId);
}
// 获取当前线程的用户ID
public static Long getUserId() {
return USER_ID_HOLDER.get();
}
// 移除当前线程的用户ID(防止内存泄漏)
public static void removeUserId() {
USER_ID_HOLDER.remove();
}
}
6. JWT 工具类(JwtUtil.java)
package com.example.token.util;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import javax.crypto.SecretKey;
import java.util.Date;
/**
* JWT Token生成/验证工具类
*/
public class JwtUtil {
// 核心密钥(生产环境建议存配置文件/环境变量,长度至少32位)
private static final String SECRET_KEY = "your_very_secure_secret_key_1234567890abcdef";
// Token过期时间:2小时(单位:毫秒)
private static final long EXPIRE_TIME = 2 * 3600 * 1000L;
/**
* 生成JWT Token
* @param userId 用户ID
* @param username 用户名
* @return JWT Token字符串
*/
public static String generateToken(Long userId, String username) {
// 生成加密密钥
SecretKey key = Keys.hmacShaKeyFor(SECRET_KEY.getBytes());
// 构建Token
return Jwts.builder()
// 自定义载荷:用户ID、用户名(非敏感信息)
.claim("userId", userId)
.claim("username", username)
// 标准字段:主题(用户唯一标识)
.setSubject(String.valueOf(userId))
// 标准字段:签发时间
.setIssuedAt(new Date())
// 标准字段:过期时间
.setExpiration(new Date(System.currentTimeMillis() + EXPIRE_TIME))
// 签名:保证Token不可篡改
.signWith(key)
// 压缩为字符串
.compact();
}
/**
* 验证并解析Token,返回用户ID
* @param token JWT Token
* @return 用户ID
*/
public static Long validateToken(String token) {
SecretKey key = Keys.hmacShaKeyFor(SECRET_KEY.getBytes());
// 解析Token(自动验证签名、过期时间)
Claims claims = Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
// 提取用户ID并返回
return Long.parseLong(claims.get("userId").toString());
}
}
7. Token 认证拦截器(AuthInterceptor.java)
package com.example.token.interceptor;
import com.example.token.exception.TokenException;
import com.example.token.util.JwtUtil;
import com.example.token.util.UserContextHolder;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Token认证拦截器:统一验证接口的Token合法性
*/
public class AuthInterceptor implements HandlerInterceptor {
/**
* 请求处理前执行:验证Token
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1. 提取请求头中的Token(标准格式:Bearer <token>)
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new TokenException(401, "未携带有效Token,请先登录");
}
// 截取"Bearer "后的Token字符串
String token = authHeader.substring(7).trim();
// 2. 验证Token并解析用户ID
Long userId;
try {
userId = JwtUtil.validateToken(token);
} catch (Exception e) {
// 捕获JWT所有异常,包装为自定义异常
throw new TokenException(401, e.getMessage());
}
// 3. 将用户ID存入ThreadLocal,供后续接口使用
UserContextHolder.setUserId(userId);
// 4. 验证通过,放行请求
return true;
}
/**
* 请求处理后执行:清理ThreadLocal,防止内存泄漏
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
UserContextHolder.removeUserId();
}
}
8. 拦截器配置类(WebMvcConfig.java)
package com.example.token.config;
import com.example.token.interceptor.AuthInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* WebMvc配置:注册拦截器,指定拦截/放行规则
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
/**
* 注册Token拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new AuthInterceptor())
// 拦截所有接口
.addPathPatterns("/**")
// 放行无需认证的接口(如登录接口)
.excludePathPatterns("/user/login");
}
}
9. 测试 Controller(UserController.java)
package com.example.token.controller;
import com.example.token.util.JwtUtil;
import com.example.token.util.UserContextHolder;
import com.example.token.vo.ResultVo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* 用户接口示例:包含登录(生成Token)、需要认证的接口
*/
@RestController
public class UserController {
/**
* 登录接口(无需认证,生成Token)
* 模拟登录:传入用户名密码,验证通过后返回Token
*/
@PostMapping("/user/login")
public ResultVo<?> login(
@RequestParam String username,
@RequestParam String password
) {
// 模拟验证用户名密码(实际开发查数据库)
if (!"zhangsan".equals(username) || !"123456".equals(password)) {
return ResultVo.fail(400, "用户名或密码错误");
}
// 模拟用户ID(实际从数据库查)
Long userId = 1001L;
// 生成JWT Token
String token = JwtUtil.generateToken(userId, username);
// 返回Token给客户端
return ResultVo.success(token);
}
/**
* 需要认证的接口:获取当前登录用户信息
*/
@GetMapping("/user/info")
public ResultVo<?> getUserInfo() {
// 从ThreadLocal获取当前登录用户ID
Long userId = UserContextHolder.getUserId();
return ResultVo.success("当前登录用户ID:" + userId + ",用户名:zhangsan");
}
}
三、运行与测试步骤
1. 启动项目
直接运行TokenApplication.java的main方法,Spring Boot 项目会启动在默认端口8080。
2. 测试接口(用 Postman/ApiPost/ 浏览器)
(1)测试登录接口(无需 Token,生成 Token)
- 请求方式:POST
- 请求地址:
http://localhost:8080/user/login - 请求参数:
username=zhangsan&password=123456 - 响应示例(成功):
{
"code": 200,
"msg": "操作成功",
"data": "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjEwMDEsInVzZXJuYW1lIjoiemhhbmdzYW4iLCJzdWIiOiIxMDAxIiwiaWF0IjoxNzM2OTYwNzY4LCJleHAiOjE3MzY5Njc5Njh9.xxxx"
}
(2)测试需要认证的接口(需携带 Token)
- 请求方式:GET
- 请求地址:
http://localhost:8080/user/info - 请求头:
Authorization: Bearer 上面生成的Token字符串 - 响应示例(成功):
{
"code": 200,
"msg": "操作成功",
"data": "当前登录用户ID:1001,用户名:zhangsan"
}
(3)测试异常场景
- 不携带 Token 访问
/user/info:返回{"code":401,"msg":"未携带有效Token,请先登录","data":null}; - 携带过期 / 篡改的 Token:返回对应的 401 异常提示。
总结
- 核心流程:登录接口生成 Token → 客户端携带 Token 请求需认证接口 → 拦截器验证 Token → 验证通过后将用户 ID 存入 ThreadLocal 供业务使用;
- 关键要点:拦截器通过
addPathPatterns/excludePathPatterns控制拦截范围,ThreadLocal 需在请求结束后清理,全局异常处理器统一返回标准响应; - 生产环境优化:密钥存入配置文件 / 环境变量、Token 过期时间可配置、Redis 存储黑名单 Token(注销 / 踢下线场景)、密码加密存储(如 BCrypt)。
更多推荐


所有评论(0)