当前位置: 首页 > 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.gsyq.cn/news/1490230.html

相关文章:

  • 星悦汇通增强缠绕结构壁管性价比如何 - myqiye
  • 别再只会用Navicat了!手把手教你用Vue2和Codemirror5.65.2搭建自己的Web版SQL编辑器
  • 技术方案初稿,可以从一次口述开始
  • 科研小白入门:从零开始用NoteExpress管理文献PDF与插入引用(保姆级图文)
  • Winhance中文版:Windows系统优化的终极免费解决方案
  • 南京口碑好的家电维修培训公司,家洁净教育上榜 - myqiye
  • 深入蜂鸟E203内核:手把手带你用VCS+Verdi调试RV32I指令执行全过程
  • 手把手教你用Qt和QScada框架,从零搭建一个简易的工业组态监控界面
  • 从数据手册到PCB:手把手复现ADS1274评估板的核心电路与布局
  • 招聘平台岗位数据采集分析与可视化实战包(BOSS直聘/拉勾/智联)
  • 针刺无纺布多少钱,炎瑞无纺性价比高吗 - mypinpai
  • 手把手教你用ESP32解析北斗/GPS模块的NMEA数据(附完整Arduino代码)
  • 手把手教你用蜂鸟E203跑通riscv-tests:从环境搭建到波形调试(含iverilog+gtwave避坑指南)
  • 物联网项目避坑:你的定位模块在室内没信号?可能是这3个原因(附EVB_Air551G室外实测对比)
  • 苹果审核2.1大礼包别慌!我从被拒到过审用了2天
  • 2026年宁波厨房设备维修专业团队综合排行全盘点:江北区空调维修、海曙区热水器维修、海曙区空调维修、鄞州区热水器维修选择指南 - 优质品牌商家
  • 别再只用针孔模型了!手把手教你用Kannala-Brandt模型搞定ORB-SLAM3鱼眼相机标定
  • 告别‘file://’权限烦恼:Android FileProvider保姆级配置与实战避坑指南
  • DzzOffice与OnlyOffice集成后,文档协作卡顿?这3个Docker性能调优参数你得改改
  • 2026年iPhone17AR护眼膜推荐:悟赫德
  • 免安装Docker镜像下载终极指南:docker-drag工具快速上手
  • 别再只用UUID v4了!5个版本(v1到v5)的实战选择指南,附Node.js代码示例
  • 服务器——终端ssh可以连接进服务器,vscode连接不进去服务器的解决办法
  • 2026年Q2杭州视频号客服外包服务商评测:杭州靠谱的客服外包团队、杭州京东客服外包、杭州全包客服、杭州全链路客服外包选择指南 - 优质品牌商家
  • Docker部署DzzOffice卡在OnlyOffice连接?手把手教你排查网络、端口和插件冲突问题
  • 2026年PP焊接土工格栅TOP5合规供应企业盘点:双向拉伸塑料格栅/土工格室/塑料土工格栅/复合土工膜/玄武岩土工格栅/选择指南 - 优质品牌商家
  • SAP PS项目状态管理实战:从‘禁止’到‘允许’,手把手教你配置WBS预算与结算权限
  • 嵌入式Linux下用C语言玩转CANopen:从心跳报文到SDO通信的保姆级实战(基于CanFestival)
  • 别再只用UUID v4了!5个版本(v1到v5)的实战选择指南与Node.js代码示例
  • 2026年价格实惠的去核机推荐厂家 - mypinpai