SpringBoot 集成 JWT 实现登录认证 + 权限拦截(电商系统实战,代码可直接复制)
一、开篇引言
做前后端分离的电商管理系统,登录认证与权限控制是绝对的核心功能,也是 Java 后端面试的高频考点。你是不是也遇到过这些问题:
前后端分离项目,Session 共享困难,无法用传统 Session 做登录认证
不知道如何实现无状态的用户登录,接口安全无法保障
分不清 JWT 的原理、结构,踩坑后无法排查问题
不知道如何结合 SpringBoot 实现登录拦截、权限校验,防止未授权访问
本文结合你正在做的电商管理系统,从原理到实战,完整实现 SpringBoot 集成 JWT 实现登录认证 + 全局权限拦截,包含完整可复制代码、高频踩坑总结,15 分钟就能搭建企业级登录认证体系,完美适配你的项目。
本文基于 SpringBoot 2.7.x + JWT 0.11.5,适合 Java 后端初学者、正在做前后端分离项目的同学,尤其适合大二计算机专业学生练手电商项目。
二、核心知识点(先懂原理,再写代码)
1. JWT 核心原理
JWT(JSON Web Token)是一种无状态的身份认证方案,核心是将用户身份信息加密后生成 Token,前端存储 Token,每次请求携带 Token 到后端,后端解密验证身份,无需在服务端存储 Session,完美适配前后端分离项目。
2. JWT 结构拆解
JWT 由 3 部分组成,用 . 分隔:
Header(头部):存储签名算法(如 HS256)、Token 类型
Payload(载荷):存储用户信息(如用户 ID、用户名)、过期时间等自定义声明
Signature(签名):用密钥对 Header+Payload 加密生成,防止 Token 被篡改
3. 核心优势
无状态:服务端无需存储 Session,扩展性强,适合分布式系统
跨域友好:完美适配前后端分离、移动端等场景
安全性高:签名机制防止 Token 被篡改,可设置过期时间保障安全
三、环境准备(直接复制,无需修改)
<!-- SpringBoot Web 核心依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- JWT 核心依赖(0.11.5 稳定版) -->
<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>
<!-- MyBatis-Plus(复用电商系统已有依赖) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3</version>
</dependency>
<!-- Lombok 简化代码 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
2. application.yml 配置
# JWT 配置
jwt:
# 密钥(生产环境请更换为复杂密钥,建议16位以上)
secret: my-secret-key-1234567890
# 过期时间(单位:毫秒,7天 = 7*24*60*60*1000 = 604800000)
expiration: 604800000
# Token 前缀
token-prefix: Bearer
# 请求头名称
header: Authorization
四、核心代码实现(严格拆分「工具类 + 拦截器 + 接口」)
4.1 JWT 工具类(核心,生成 / 解析 Token)
package com.example.demo.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
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;
/**
* JWT 工具类
* 用于生成 Token、解析 Token、验证 Token 有效性
*/
@Component
public class JwtUtils {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
@Value("${jwt.token-prefix}")
private String tokenPrefix;
/**
* 生成密钥
*/
private SecretKey getSignKey() {
return Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
}
/**
* 生成 Token
* @param userId 用户ID
* @param username 用户名
* @return 生成的 Token
*/
public String generateToken(Long userId, String username) {
Map<String, Object> claims = new HashMap<>();
claims.put("userId", userId);
claims.put("username", username);
return createToken(claims, username);
}
/**
* 创建 Token
*/
private String createToken(Map<String, Object> claims, String subject) {
Date now = new Date();
Date expireDate = new Date(now.getTime() + expiration);
return Jwts.builder()
.setClaims(claims) // 自定义载荷
.setSubject(subject) // 主题
.setIssuedAt(now) // 签发时间
.setExpiration(expireDate) // 过期时间
.signWith(getSignKey(), SignatureAlgorithm.HS256) // 签名
.compact();
}
/**
* 解析 Token,获取载荷
*/
public Claims parseToken(String token) {
return Jwts.parserBuilder()
.setSigningKey(getSignKey())
.build()
.parseClaimsJws(token)
.getBody();
}
/**
* 从 Token 中获取用户ID
*/
public Long getUserIdFromToken(String token) {
Claims claims = parseToken(token);
return Long.valueOf(claims.get("userId").toString());
}
/**
* 验证 Token 是否过期
*/
public Boolean isTokenExpired(String token) {
Claims claims = parseToken(token);
return claims.getExpiration().before(new Date());
}
/**
* 验证 Token 有效性
*/
public Boolean validateToken(String token) {
try {
return !isTokenExpired(token);
} catch (Exception e) {
return false;
}
}
/**
* 去除 Token 前缀
*/
public String removePrefix(String token) {
if (token.startsWith(tokenPrefix)) {
return token.substring(tokenPrefix.length()).trim();
}
return token;
}
}
4.2 JWT 拦截器(全局权限拦截,验证 Token)
package com.example.demo.interceptor;
import com.example.demo.utils.JwtUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* JWT 拦截器
* 拦截所有请求,验证 Token 有效性,实现登录认证
*/
@Component
public class JwtInterceptor implements HandlerInterceptor {
@Resource
private JwtUtils jwtUtils;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1. 处理跨域预检请求(OPTIONS 直接放行)
if ("OPTIONS".equals(request.getMethod())) {
return true;
}
// 2. 从请求头获取 Token
String token = request.getHeader("Authorization");
// 3. Token 为空,返回未授权
if (token == null || token.isEmpty()) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("{\"code\":401,\"msg\":\"未登录,请先登录\"}");
return false;
}
// 4. 去除 Token 前缀
token = jwtUtils.removePrefix(token);
// 5. 验证 Token 有效性
if (!jwtUtils.validateToken(token)) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("{\"code\":401,\"msg\":\"Token 已过期,请重新登录\"}");
return false;
}
// 6. Token 有效,放行请求
return true;
}
}
4.3 拦截器配置类(注册拦截器,配置放行路径)
package com.example.demo.config;
import com.example.demo.interceptor.JwtInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.annotation.Resource;
/**
* WebMvc 配置类
* 注册 JWT 拦截器,配置拦截/放行路径
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Resource
private JwtInterceptor jwtInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtInterceptor)
// 拦截所有请求
.addPathPatterns("/**")
// 放行登录、注册等接口(电商系统核心放行路径)
.excludePathPatterns(
"/user/login", // 登录接口
"/user/register", // 注册接口
"/captcha/get", // 验证码接口
"/doc.html", // Knife4j 接口文档
"/webjars/**",
"/v3/api-docs/**"
);
}
}
4.4 登录接口实现(电商系统用户登录)
package com.example.demo.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.example.demo.common.Result;
import com.example.demo.entity.User;
import com.example.demo.service.UserService;
import com.example.demo.utils.JwtUtils;
import org.springframework.util.DigestUtils;
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 javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
/**
* 用户登录控制器
* 实现电商系统用户登录,生成 JWT Token
*/
@RestController
@RequestMapping("/user")
public class LoginController {
@Resource
private UserService userService;
@Resource
private JwtUtils jwtUtils;
/**
* 用户登录接口
* @param user 登录信息(username、password)
* @return 登录结果 + Token
*/
@PostMapping("/login")
public Result<Map<String, Object>> login(@RequestBody User user) {
// 1. 校验用户名密码非空
if (user.getUsername() == null || user.getPassword() == null) {
return Result.fail("用户名或密码不能为空");
}
// 2. 根据用户名查询用户
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getUsername, user.getUsername())
.eq(User::getDeleted, 0);
User existUser = userService.getOne(wrapper);
// 3. 用户不存在
if (existUser == null) {
return Result.fail("用户名不存在");
}
// 4. 密码校验(实际项目用 BCrypt 加密,此处用 MD5 演示)
String inputPwd = DigestUtils.md5DigestAsHex(user.getPassword().getBytes());
if (!inputPwd.equals(existUser.getPassword())) {
return Result.fail("密码错误");
}
// 5. 生成 JWT Token
String token = jwtUtils.generateToken(existUser.getId(), existUser.getUsername());
// 6. 返回登录结果
Map<String, Object> result = new HashMap<>();
result.put("token", token);
result.put("user", existUser);
return Result.success(result);
}
}
五、接口测试效果演示
1. 登录请求
请求:POST /user/login请求体:
{
"username": "zhangsan",
"password": "123456"
}
返回
{
"code": 200,
"msg": "操作成功",
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": 1,
"username": "zhangsan",
"nickname": "张三",
"status": 1
}
}
}
2. 带 Token 请求用户接口
请求头:Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…请求:GET /user/1返回:
{
"code": 200,
"msg": "操作成功",
"data": {
"id": 1,
"username": "zhangsan",
"nickname": "张三"
}
}
3. 无 Token / 过期 Token 请求
返回:
{
"code": 401,
"msg": "未登录,请先登录"
}
六、高频踩坑总结(新手必看)
坑 1:跨域预检请求(OPTIONS)被拦截
原因:拦截器没有放行 OPTIONS 请求,导致跨域失败
解决方案:在拦截器中添加 if (“OPTIONS”.equals(request.getMethod())) { return true; },直接放行预检请求
坑 2:Token 过期时间设置不合理
原因:过期时间太短,用户频繁登录;太长,安全性低
解决方案:电商系统建议设置 7 天过期,同时实现 Token 刷新机制(后续可扩展)
坑 3:密钥泄露,Token 被篡改
原因:密钥过于简单,生产环境硬编码在代码中
解决方案:密钥设置为 16 位以上复杂字符串,生产环境通过配置中心管理,禁止硬编码
坑 4:载荷存储敏感信息
原因:JWT 载荷是 Base64 编码,可直接解码,不能存储密码等敏感信息
解决方案:载荷仅存储用户 ID、用户名等非敏感信息,敏感信息绝对不能存入
坑 5:拦截器放行路径配置错误
原因:登录、验证码等接口被拦截,导致无法登录
解决方案:严格配置放行路径,确保登录、注册、验证码等接口不受拦截
七、总结与延伸
本文完整实现了 SpringBoot 集成 JWT 实现登录认证 + 全局权限拦截,完美适配电商管理系统的登录需求,所有代码可直接复制到你的项目中使用。通过本文的实现,你可以:
彻底解决前后端分离项目的登录认证问题,实现无状态身份校验
掌握 JWT 的核心原理与实战用法,应对面试高频考点
搭建企业级登录认证体系,保障接口安全
为后续权限控制(RBAC)、角色管理打下基础
本文是电商管理系统实战系列的第 4 篇,上一篇讲解了 MyBatis-Plus 条件查询避坑,下一篇将讲解 SpringBoot 集成 Redis 实现缓存优化,感兴趣的同学可以关注我,持续更新 Java 后端实战内容。
八、互动引导
你在集成 JWT 时还遇到过哪些问题?欢迎在评论区留言,我会逐一解答!觉得有用的同学,点赞 + 收藏不迷路,后续更新更多 Java 后端学习、项目实战、面试技巧!关注我,持续分享 Java 后端干货,陪你从大二到秋招拿 offer!
更多推荐


所有评论(0)