当前位置: 首页 > news >正文

JWT登录认证系统​ —— 用户注册/登录 + 接口保护

JWT = JSON Web Token,是目前主流的登录身份认证方案,用来替代传统的 Session。

核心作用:

用户登录后,服务端生成一段加密字符串(JWT 令牌)发给客户端; 客户端后续每次请求接口,带上这个令牌,服务端就能识别「你是谁、是否登录、权限」

拦截器,充当门卫角色,请求进入Controller之前先验token,没有登录直接挡回去。

首先再创建一个sys_user表

CREATE TABLE sys_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL COMMENT '登录账号', password VARCHAR(100) NOT NULL COMMENT '加密后的密码', role VARCHAR(20) DEFAULT 'USER' COMMENT '角色:ADMIN/USER', create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
插入一个测试账号(密码明文123456,后面代码里会加密)
VALUES('admin', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iAt6Z5EO', 'ADMIN');

在前一个项目的基础上新建文件

pom.xml → 加了JWT依赖
entity/SysUser.java → 用户实体
mapper/SysUserMapper.java → 查用户/插用户
utils/JwtUtil.java → 生成token/验token
service/AuthService.java → 注册登录业务
controller/AuthController.java → 对外暴露接口
interceptor/JwtInterceptor.java → 验token拦截器
config/WebConfig.java → 注册拦截器

修改pom.xml

新增:

<!-- JWT依赖 --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.12.3</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.12.3</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.12.3</version> <scope>runtime</scope> </dependency> <!-- 密码加密 --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-crypto</artifactId> </dependency>

在entity包中新建用户实体类SysUser.java

package com.example.excel_import.entity; import lombok.Data; import java.time.LocalDateTime; @Data//自动生成getter和setter方法 public class SysUser { private Integer id; //用户ID private String username; //用户名 private String password; //密码 private String role; //角色 private LocalDateTime createTime;//创建时间 }

在mapper包中创建SysUserMapper接口

package com.example.excel_import.mapper; import com.example.excel_import.entity.SysUser; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; @Mapper public interface SysUserMapper { // 根据用户名查询用户(登录用) @Select("SELECT * FROM sys_user WHERE username = #{username}") SysUser findByUsername(String username); // 注册用户(插入新用户) @Insert("INSERT INTO sys_user(username, password, role) VALUES(#{username}, #{password}, #{role})") void insert(SysUser user); }

新建utils包,创建JwtUtil类 生成token/验token

package com.example.excel_import.utils; import io.jsonwebtoken.*; import io.jsonwebtoken.security.Keys; import javax.crypto.SecretKey; import java.nio.charset.StandardCharsets; import java.util.Date; public class JwtUtil { // 密钥,实际项目要放到配置文件里,别写死在代码里 private static final String SECRET = "your-secret-key-your-secret-key-your-secret-key"; // 过期时间:24小时(毫秒) private static final long EXPIRATION = 86400000; // 生成密钥对象 private static final SecretKey KEY = Keys.hmacShaKeyFor(SECRET.getBytes(StandardCharsets.UTF_8)); /** * 生成JWT令牌 * @param userId 用户ID * @param username 用户名 * @param role 角色 * @return JWT字符串 */ public static String generateToken(Integer userId, String username, String role) { Date now = new Date(); Date expiration = new Date(now.getTime() + EXPIRATION); return Jwts.builder() .subject(String.valueOf(userId)) // 主题:存用户ID .claim("username", username) // 自定义字段:用户名 .claim("role", role) // 自定义字段:角色 .issuedAt(now) // 签发时间 .expiration(expiration) // 过期时间 .signWith(KEY) // 签名 .compact(); // 生成字符串 } /** * 解析JWT令牌 * @param token 令牌字符串 * @return Claims对象(包含用户信息) */ public static Claims parseToken(String token) { return Jwts.parser() .verifyWith(KEY) .build() .parseSignedClaims(token) .getPayload(); } /** * 验证令牌是否有效 * @param token 令牌字符串 * @return true=有效,false=无效或过期 */ public static boolean validateToken(String token) { try { parseToken(token); return true; } catch (ExpiredJwtException e) { System.out.println("令牌已过期"); return false; } catch (Exception e) { System.out.println("令牌无效:" + e.getMessage()); return false; } } }

在service包中创建AuthService类 注册登录业务

package com.example.excel_import.service; import com.example.excel_import.entity.SysUser; import com.example.excel_import.mapper.SysUserMapper; import com.example.excel_import.utils.JwtUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; import java.util.HashMap; import java.util.Map; @Service public class AuthService { @Autowired private SysUserMapper sysUserMapper; // 密码加密器 private BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); /** * 用户注册 */ public Map<String, Object> register(String username, String password) { Map<String, Object> result = new HashMap<>(); // 1. 检查账号是否已存在 SysUser existUser = sysUserMapper.findByUsername(username); if (existUser != null) { result.put("success", false); result.put("message", "账号已存在"); return result; } // 2. 加密密码 String encodedPassword = encoder.encode(password); // 3. 创建用户 SysUser user = new SysUser(); user.setUsername(username); user.setPassword(encodedPassword); user.setRole("USER"); // 默认普通用户 sysUserMapper.insert(user); result.put("success", true); result.put("message", "注册成功"); return result; } /** * 用户登录 */ public Map<String, Object> login(String username, String password) { Map<String, Object> result = new HashMap<>(); // 1. 查询用户 SysUser user = sysUserMapper.findByUsername(username); if (user == null) { result.put("success", false); result.put("message", "账号不存在"); return result; } // 2. 校验密码(明文 vs 密文) if (!encoder.matches(password, user.getPassword())) { result.put("success", false); result.put("message", "密码错误"); return result; } // 3. 生成JWT令牌 String token = JwtUtil.generateToken(user.getId(), user.getUsername(), user.getRole()); result.put("success", true); result.put("message", "登录成功"); result.put("token", token); // 前端要存这个token result.put("role", user.getRole()); return result; } }

在controller包中创建AuthController类

package com.example.excel_import.controller; import com.example.excel_import.service.AuthService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.Map; @RestController @RequestMapping("/auth") public class AuthController { @Autowired private AuthService authService; // 注册接口 @PostMapping("/register") public Map<String, Object> register( @RequestParam String username, @RequestParam String password) { return authService.register(username, password); } // 登录接口 @PostMapping("/login") public Map<String, Object> login( @RequestParam String username, @RequestParam String password) { return authService.login(username, password); } }


新建interceptor包,在包中创建JwtInterceptor类 检验token拦截器

package com.example.excel_import.interceptor; import com.example.excel_import.utils.JwtUtil; import io.jsonwebtoken.Claims; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; @Component//标识这是一个Spring组件,可以被Spring容器管理,自动装配 public class JwtInterceptor implements HandlerInterceptor { @Override//标识这是一个重写方法 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 1. 从请求头取token String token = request.getHeader("Authorization"); // 2. 没有token直接拒绝 if (token == null || !token.startsWith("Bearer ")) { response.setStatus(401); response.getWriter().write("{\"success\":false,\"message\":\"请先登录\"}"); return false; } // 3. 去掉"Bearer "前缀 token = token.substring(7); // 4. 验证token if (!JwtUtil.validateToken(token)) { response.setStatus(401); response.getWriter().write("{\"success\":false,\"message\":\"登录已过期,请重新登录\"}"); return false; } // 5. 解析用户信息,存到request里供后面用 Claims claims = JwtUtil.parseToken(token); request.setAttribute("userId", claims.getSubject()); request.setAttribute("username", claims.get("username")); request.setAttribute("role", claims.get("role")); return true; // 放行 } }


在config包中创建WebConfig类 注册拦截器

package com.example.excel_import.config; import com.example.excel_import.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( // 排除这些接口(公开访问) "/auth/register", "/auth/login" ); } }

数据流向注册:账号密码 → BCrypt加密 → 存数据库
登录:账号密码 → 查数据库 → BCrypt比对 → 生成JWT → 返回前端
后续请求:带token → 拦截器验签 → 放行到Controller

运行此项目

测试验证清单

  • POST/auth/register能注册新用户

  • POST/auth/login能登录并返回token

  • GET/api/users不带token返回401

  • GET/api/users带正确token返回数据

打开Postman


POST url:http://localhost:8080/auth/register

Body中form-data中

key:username value:test

key:password value:123456

点击SEND


POST url:http://localhost:8080/auth/login

Body中form-data中

key:username value:test

key:password value:123456

点击SEND(获取token)


GET url:http://localhost:8080/api/users

Headers中新增

key:Authorization

Value:Bearer +生成的token

例如:

Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI0IiwidXNlcm5hbWUiOiJ0ZXN0Iiwicm9sZSI6IlVTRVIiLCJpYXQiOjE3ODA4ODE4ODIsImV4cCI6MTc4MDk2ODI4Mn0.db13bq5lDlnD3dsZQumx5QK7_nsyGvfeHYVCRu0pypE

http://www.jsqmd.com/news/978502/

相关文章:

  • 告别命令行恐惧症:用Portainer在5分钟内搞定Docker容器管理(保姆级图文教程)
  • 星悦汇通增强缠绕结构壁管性价比如何 - myqiye
  • 硬件工程师必看:从MII到RGMII,手把手教你搞定以太网PHY与MAC的PCB布局布线(含阻抗控制与等长设计)
  • AI 太阳能智慧灯具高效智能功率 MOSFET 完整选型方案
  • 别再只会用Navicat了!手把手教你用Vue2和Codemirror5.65.2搭建自己的Web版SQL编辑器
  • Windows 下 Claude Code 接入 DeepSeek 与 Cowork 故障排查实录
  • 从‘通道打乱’到‘通道分割’:图解ShuffleNet V1/V2的核心演进与PyTorch实现细节
  • 数据说话:低代码为何能省下七成开发成本
  • 南京口碑好的家电维修培训公司,家洁净教育上榜 - myqiye
  • 别再死磕Pytorch3D官方指南了!我的Linux(Ubuntu 20.04)保姆级安装避坑全记录
  • 科研小白入门:从零开始用NoteExpress管理文献PDF与插入引用(保姆级图文)
  • 技术方案初稿,可以从一次口述开始
  • Winhance中文版:Windows系统优化的终极免费解决方案
  • 别再手动改Excel了!用Python的openpyxl库批量处理单元格数据(附完整代码)
  • 【汽车雷达】基于线性调频脉冲(LMCW)雷达仿真(Matlab代码实现)
  • 从设计稿到完美还原:手把手教你定制el-table样式,搞定UI设计师的‘像素眼’
  • 别再手动输坐标了!Excel表格一键导入Arcmap生成点图层(附坐标转换公式)
  • 深入蜂鸟E203内核:手把手带你用VCS+Verdi调试RV32I指令执行全过程
  • 跟着 MDN 学JavaScript day_10:数组——数据的有序集合
  • 全志 T113-i 截屏调试记录
  • 手把手教你用Qt和QScada框架,从零搭建一个简易的工业组态监控界面
  • 如何解决区域企业技术需求挖掘不精准的问题?
  • 2026年,揭秘天水废铜回收,哪家才是行业黑马?
  • 从ESP-01S到ESP-12F:一个毕业生的物联网上云踩坑实录(附完整接线图)
  • 口碑好的过滤料厂家有哪些,三山鹅卵石厂上榜了吗? - mypinpai
  • 2026 小程序行业发展全景洞察:技术迭代与商业落地趋势解析
  • 从数据手册到PCB:手把手复现ADS1274评估板的核心电路与布局
  • 别再死记硬背了!用FFmpeg实战拆解音视频面试高频考点(附避坑指南)
  • 招聘平台岗位数据采集分析与可视化实战包(BOSS直聘/拉勾/智联)
  • Cesium画点总被‘吃掉’一半?别慌,这3个方法帮你搞定(附代码示例)