在前后端分离的开发模式下,传统的 Session 方案显得力不从心:跨域问题难搞、服务器集群要做 Session 共享、移动端支持不友好……
这时候,JWT (JSON Web Token) 就成了最佳替代方案。
今天这篇文章不讲废话,直接带你在 Spring Boot 项目中从零实现一套标准、可用的 JWT 认证系统。
1. 为什么选 JWT?
简单来说,Session 是把用户信息存在服务端(内存或数据库),发给客户端一个 JSESSIONID;而 JWT 是把用户信息加密后存在客户端(Token)。
无状态 (Stateless):服务端不存 Token,服务器随便横向扩展,不需要复杂的 Redis Session 共享配置。
跨终端:iOS、Android、小程序、Web 通用。
2. 准备工作
首先,在你的 pom.xml 中引入 JWT 的工具包 jjwt。这是目前 Java 界最常用的 JWT 库。
XML
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
3. 核心三步走
实现 JWT 认证只需要三个核心组件:工具类、拦截器、配置类。
第一步:编写 JWT 工具类 (JwtUtils)
这个类负责生成 Token 和解析 Token。我们不仅要生成,还要把生成的 Token 里的用户信息(Claims)取出来。
Java
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Component
public class JwtUtils {
// 密钥,非常重要,实际生产中请放在配置文件中,不要硬编码
private static final String SECRET_KEY = "MySuperSecretKeyForMyBlogApp";
// 过期时间:24小时 (毫秒)
private static final long EXPIRATION_TIME = 86400000;
/**
* 生成 Token
*/
public String generateToken(String username) {
Map<String, Object> claims = new HashMap<>();
return Jwts.builder()
.setClaims(claims)
.setSubject(username) // 设置主题(这里存用户名)
.setIssuedAt(new Date(System.currentTimeMillis())) // 签发时间
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) // 过期时间
.signWith(SignatureAlgorithm.HS256, SECRET_KEY) // 签名算法和密钥
.compact();
}
/**
* 解析 Token,获取 Claims
*/
public Claims extractClaims(String token) {
return Jwts.parser()
.setSigningKey(SECRET_KEY)
.parseClaimsJws(token)
.getBody();
}
/**
* 验证 Token 是否有效
*/
public boolean validateToken(String token, String username) {
final String extractedUsername = extractClaims(token).getSubject();
return (extractedUsername.equals(username) && !isTokenExpired(token));
}
private boolean isTokenExpired(String token) {
return extractClaims(token).getExpiration().before(new Date());
}
}
第二步:编写拦截器 (JwtInterceptor)
拦截器是“门卫”。它的作用是拦截所有请求,检查 Header 里有没有 Token,以及 Token 对不对。
Java
import io.jsonwebtoken.Claims;
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;
@Component
public class JwtInterceptor implements HandlerInterceptor {
@Autowired
private JwtUtils jwtUtils;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1. 获取请求头中的 Authorization
String authHeader = request.getHeader("Authorization");
// 2. 判断格式是否正确 (通常是 "Bearer " 开头)
if (authHeader != null && authHeader.startsWith("Bearer ")) {
String token = authHeader.substring(7); // 去掉 "Bearer "
try {
// 3. 尝试解析 Token
Claims claims = jwtUtils.extractClaims(token);
// 4. 将解析出的用户信息放入 Request 域,方便 Controller 使用
request.setAttribute("username", claims.getSubject());
return true; // 放行
} catch (Exception e) {
// Token 无效或过期
response.setStatus(401);
return false;
}
}
// 没有 Token,返回 401 未授权
response.setStatus(401);
return false;
}
}
第三步:配置拦截器 (WebConfig)
写好了门卫,得告诉它守哪扇门。我们需要配置拦截路径。
Java
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("/api/**") // 拦截所有 /api 开头的请求
.excludePathPatterns("/api/login", "/api/register"); // 放行登录和注册接口
}
}
4. 实战演示
现在,我们来看在 Controller 中怎么用。
登录接口 (不需要 Token):
Java
@PostMapping("/login")
public Result login(@RequestBody UserLoginDto user) {
// 1. 校验用户名密码 (这里省略数据库查询逻辑)
if ("admin".equals(user.getUsername()) && "123456".equals(user.getPassword())) {
// 2. 校验通过,生成 Token
String token = jwtUtils.generateToken(user.getUsername());
return Result.success(token);
}
return Result.fail("用户名或密码错误");
}
业务接口 (需要 Token):
Java
@GetMapping("/user/profile")
public Result getProfile(HttpServletRequest request) {
// 从拦截器塞入的属性中获取用户名
String username = (String) request.getAttribute("username");
return Result.success("当前登录用户是:" + username);
}
5. 进阶避坑指南 (Senior Tips)
做完上面这些,一个基本的 JWT 认证就完成了。但在生产环境中,还有几个坑要注意:
Token 无法撤销的问题:
JWT 一旦签发,在过期之前都是有效的。如果用户想“注销登录”或者修改了密码,旧 Token 依然可用。
解决方案:引入 Redis。用户注销时,把这个 Token 的 ID 存入 Redis 设置为黑名单,拦截器校验时多查一步 Redis。
不要在 Payload 存敏感信息:
JWT 的 Payload 部分只是 Base64 编码,没有加密!任何人拿到 Token 都能解码看到 Payload 里的内容。千万不要把密码、手机号放在这里。
刷新 Token (Refresh Token):
为了安全,Access Token 有效期通常很短(如 30 分钟)。为了不让用户频繁登录,通常会配合一个有效期较长的 Refresh Token 来换取新的 Access Token。
总结
通过 jjwt + HandlerInterceptor,我们在 Spring Boot 中轻松实现了无状态认证。这套方案轻量、高效,是目前微服务和前后端分离项目的首选。
接下来的文章,我们将结合 Redis,讲讲如何解决 JWT 最大的痛点——Token 的注销与续签。
原文链接:https://makule.top/