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

spring boot2 +java-jwt轻量实现jwt

前言 

  1. 对于 Spring Boot 项目

    • 如果已经在使用 Spring Security,优先考虑 JJWT,因为它与 Spring 生态系统更兼容
    • 如果希望代码更简洁,或者需要与 Auth0 服务集成,考虑 java-jwt
  2. 对于非 Spring 项目

    • java-jwt 通常是更好的选择,因为它更轻量、API 更现代
  3. 对于初学者

    • java-jwt 的链式 API 更容易理解和使用

使用 java-jwt 实现 Spring Boot 2.7.13 项目的 JWT 认证

我们先不使用spring secrity 框架,搞个更轻量的

一、创建 JWT 工具类

首先创建一个工具类来处理 JWT 的生成和验证:

package com.neuedu.hisweb.utils;import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.JWTVerifier;
import com.neuedu.hisweb.entity.Customer;
import com.neuedu.hisweb.entity.User;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;import java.util.Date;/*** JWT工具类,用于生成和验证JSON Web Token** JWT由三部分组成:* 1. Header: 包含令牌类型和签名算法* 2. Payload: 包含用户信息和元数据* 3. Signature: 用于验证令牌的完整性** 格式: Header.Payload.Signature*/
@Component
public class JwtUtils {// 签名密钥,用于生成和验证JWT签名public static final String SECRET = "SECRET";// 应用级别的密钥,用于额外的安全验证private String secretkey;// JWT过期时间(秒),从配置文件注入,默认1年private Long expireTime;// 从配置文件中注入应用密钥@Value("${jwt.secretkey}")public void setSecretkey(String secretkey) {this.secretkey = secretkey;}// 从配置文件中注入JWT过期时间,默认值为1年(31536000秒)@Value("${jwt.expireTime:31536000}")public void setExpireTime(Long expireTime) {this.expireTime = expireTime;}/*** 根据用户对象生成JWT令牌** @param object 用户对象,可以是User或Customer类型* @return 生成的JWT令牌*/public String sign(Object object) {// 计算过期时间(毫秒),将配置的秒转换为毫秒Date expireDate = new Date(System.currentTimeMillis() + expireTime * 1000);// 创建JWT构建器,添加通用声明JWTCreator.Builder builder = JWT.create().withClaim("secretkey", secretkey)  // 添加应用密钥作为声明.withExpiresAt(expireDate);  // 设置过期时间// 根据用户类型添加不同的声明if (object instanceof Customer) {Customer customer = (Customer) object;return builder.withClaim("id", customer.getId())  // 添加客户ID.sign(Algorithm.HMAC256(SECRET));  // 使用HMAC256算法签名} else if (object instanceof User) {User user = (User) object;return builder.withClaim("realName", user.getRealName())  // 添加真实姓名.withClaim("userName", user.getUserName())  // 添加用户名.withClaim("id", user.getId())  // 添加用户ID.sign(Algorithm.HMAC256(SECRET));  // 使用HMAC256算法签名}// 如果对象类型不支持,抛出异常throw new IllegalArgumentException("Unsupported object type: " + object.getClass().getName());}/*** 验证JWT令牌的有效性** @param token 待验证的JWT令牌* @return 验证结果,true表示有效,false表示无效*/public boolean verify(String token) {try {// 创建JWT验证器,使用相同的密钥和算法JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET)).build();// 验证令牌,如果验证失败会抛出异常verifier.verify(token);return true;} catch (JWTVerificationException e) {// 捕获验证异常,返回验证失败return false;}}/*** 从JWT令牌中获取用户名** @param token JWT令牌* @return 用户名,如果令牌无效则返回null*/public String getUserNameByToken(String token) {try {// 解码JWT令牌,获取声明信息DecodedJWT decodedJWT = JWT.decode(token);// 获取userName声明return decodedJWT.getClaim("userName").asString();} catch (JWTDecodeException e) {// 处理解码异常,返回null表示获取失败return null;}}/*** 从JWT令牌中获取用户对象** @param token JWT令牌* @return 用户对象(User或Customer),如果令牌无效则返回null*/public Object getUserByToken(String token) {try {// 解码JWT令牌,获取声明信息DecodedJWT decodedJWT = JWT.decode(token);// 根据是否存在userName声明判断用户类型if (decodedJWT.getClaim("userName").isNull()) {// 没有userName声明,创建Customer对象Customer customer = new Customer();customer.setId(decodedJWT.getClaim("id").asInt());return customer;}// 有userName声明,创建User对象User user = new User();user.setUserName(decodedJWT.getClaim("userName").asString());user.setRealName(decodedJWT.getClaim("realName").asString());user.setId(decodedJWT.getClaim("id").asInt());return user;} catch (JWTDecodeException e) {// 处理解码异常,返回null表示获取失败return null;}}/*** 验证JWT令牌并返回解码后的JWT对象** @param token JWT令牌* @return 解码后的JWT对象*/private DecodedJWT verifyAndGetJWT(String token) {// 创建验证器并验证令牌JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET)).build();return verifier.verify(token);}
}
verify 方法 

verify 方法验证 JWT 的原理,本质是检查令牌的完整性、合法性以及时效性,确保令牌是服务端签发且未被篡改、未过期,核心围绕 JWT 的结构和签名机制展开,用大白话详细拆解如下:

1. 先理解 JWT 的 “身份”:三部分组成

JWT 令牌本质是个字符串,格式为 Header.Payload.Signature(三部分用 . 拼接):

  • Header(头):存令牌类型(固定 JWT)和签名算法(比如这里的 HMAC256),格式是 JSON,会被 Base64 编码。
  • Payload(载荷):存业务数据(比如用户 ID、用户名)和元数据(比如过期时间 exp),也是 JSON 后 Base64 编码。
  • Signature(签名):用 Header 里的算法 + 服务端密钥,对 Header.Payload 进行加密生成,用于防篡改。
2. verify 验证的核心逻辑
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET)).build();
verifier.verify(token);

这两行代码做了这些事,最终实现 “验证令牌是否合法”:

(1)“搭环境”:准备验证器

JWT.require(Algorithm.HMAC256(SECRET)).build()

  • 告诉验证器:“用 HMAC256 算法,且用服务端的 SECRET 密钥” 。
  • 相当于给验证器配好 “解密 / 验签工具”,让它知道怎么去核对令牌的签名。
(2)“验身份”:检查令牌是否合法

verifier.verify(token) 会依次做这些校验(只要有一个不通过,就抛 JWTVerificationException):

  • ① 检查签名是否被篡改
    验证器会按 JWT 格式,把令牌拆成 HeaderPayloadSignature 三部分。
    然后用和签发时相同的算法(HMAC256)+ 相同的密钥(SECRET),重新计算 Header.Payload 的签名。

    • 如果重新计算的签名 ≠ 令牌里的 Signature,说明令牌被改过(比如 Payload 里的用户 ID 被偷偷改了),验证失败。
  • ② 检查令牌是否过期
    验证器会解析 Payload 里的 exp(过期时间)字段,对比当前系统时间:

    • 如果 当前时间 > exp,说明令牌过期,验证失败。
  • ③ 检查其他 “合法性”(可选,这里代码没配,但原理通用)
    除了签名和过期,还能校验更多规则(比如检查 iss 发行人、aud 受众是否符合预期),不过你代码里没配这些,所以主要校验前两项。

3. 总结验证原理

简单说,verify 就是:
和签发时相同的算法 + 密钥,重新生成签名,对比令牌里的签名(防篡改);同时检查令牌里的过期时间(防过期)。
只有这两项(以及其他你配置的规则)都通过,才认为令牌合法,返回 true;否则返回 false

可以理解成:
把 JWT 当成一张 “身份证”,verify 就是 “警察叔叔”:

  • 先看身份证上的 “防伪标记”(签名)对不对 → 防篡改。
  • 再看 “有效期” 过没过期 → 防过期。
  • 都没问题,才承认这张 “身份证” 是真的 。

二、添加配置属性

在 application.properties 中添加 JWT 相关配置:

# 密钥配置
#secretkey: hisweb
jwt:secretkey: hisweb  # 应用级别的密钥,用于额外安全验证expireTime: 3600                # JWT过期时间(秒),默认值为1年(31536000)

三、用户登录发Token

  1. 写登录接口

      @PostMapping("/login")public JsonResult<User> login(HttpServletRequest request, @RequestBody User user){String uname = user.getUserName();String pwd = user.getPassword();// 构建查询条件:用户名、密码匹配且未删除LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();wrapper.eq(User::getUserName, uname).eq(User::getPassword, pwd).eq(User::getDelMark, 1);// 调用服务层查询用户user = iUserService.getOne(wrapper);JsonResult<User> jsonResult;if (user == null) {// 登录失败jsonResult = new JsonResult<User>("用户名或密码不正确!");} else {// 登录成功,将用户信息存入会话request.getSession().setAttribute("user", user);// 生成JWT令牌(通过注入的jwtUtils实例调用sign方法)String token = jwtUtils.createToken(user);// 返回用户信息和令牌jsonResult = new JsonResult<>(user, token);}return jsonResult;}
     

四、保护其他接口

  1. 写个拦截器检查token

package com.neuedu.hisweb.interceptor;import com.neuedu.hisweb.entity.Customer;
import com.neuedu.hisweb.entity.JsonResult;
import com.neuedu.hisweb.entity.User;
import com.neuedu.hisweb.utils.JwtUtils;
import com.neuedu.hisweb.utils.UserUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;// 添加@Component注解,让Spring管理这个拦截器
@Component
public class JwtInterceptor implements HandlerInterceptor {private static final Logger logger = LoggerFactory.getLogger(JwtInterceptor.class);// 注入JwtUtils实例@Autowiredprivate JwtUtils jwtUtils;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String token = request.getHeader("token");if (!(handler instanceof HandlerMethod)) {return true;}// 通过注入的实例调用verify方法if (null == token || "".equals(token) || !jwtUtils.verify(token)) {response.setCharacterEncoding("UTF-8");response.setContentType("application/json; charset=utf-8");try (PrintWriter writer = response.getWriter()) {writer.print(new JsonResult<User>("未登录"));} catch (Exception e) {logger.error("login token error is {}", e.getMessage());}return false;}// 通过注入的实例调用getUserByToken方法Object userObj = jwtUtils.getUserByToken(token);if (userObj instanceof Customer){UserUtils.setLoginCustomer((Customer) userObj);}else{UserUtils.setLoginUser((User) userObj);}return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println("执行了拦截器的postHandle方法");}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {UserUtils.removeUser();}
}
  1. 注册拦截器

    @Configuration
    public class WebConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new JwtInterceptor()).addPathPatterns("/api/**") // 保护所有/api开头的接口.excludePathPatterns("/login"); // 不拦截登录接口}
    }
     

五、测试使用

  1. 写个测试接口

    @RestController
    @RequestMapping("/api")
    public class TestController {@GetMapping("/hello")public String hello() {return "需要token才能访问的数据";}
    }
     
  2. 测试步骤

    • 先用Postman访问 /login 获取token

    • 访问 /api/hello 时,在Headers加:

      Authorization: Bearer 你的token

六、注意事项

  1. 密钥保管好:别把密钥写在代码里,可以放配置文件

  2. token过期:前端发现401错误要自动跳转到登录页

  3. 敏感操作:重要操作(如修改密码)即使有token也要再输密码

http://www.xdnf.cn/news/978085.html

相关文章:

  • AI中间件,构建大模型应用的标准化接入枢纽
  • 文献管理软件EndNote下载与安装教程(详细教程)2025最新版详细图文安装教程
  • 2025年- H80-Lc188--198.打家劫舍(动态规划)--Java版
  • 前端基础知识ES6系列 - 03(数组新增了哪些扩展)
  • SCADA|信创KingSCADA4.0与KingSCADA3.8的几点不同
  • 基于微信小程序的天气预报app
  • 一键批量修改XML标签名称:告别手工修改,高效管理标注数据
  • LangChain文档加载器自动选择器:支持多种文件格式的统一加载方法
  • jupyter中的checkpoints为空/打不开解决办法
  • [Java 基础]Math 类
  • SnapViewer:解决PyTorch官方内存工具卡死问题,实现高效可视化
  • Android Studio 无法安装HAXM,点击之后无反应
  • 链 表 类 型 全 面 总 结:单 向、双 向、循 环 链 表 的 特 性 与 选 型 指 南
  • 系统入侵排查实战指南:从Windows到Linux的应急响应与溯源分析​
  • Qt 中directoryChanged监听某个目录的内容是否发生变化
  • 达梦数据库DCA考试命令行操作
  • flink1.19.2+cdc-3.2.1遇到的问题及解决方案
  • Ubuntu 实现可视化组raid和升级raid
  • 成功在 Conda Python 2.7 环境中安装 Clipper(eCLIP peak caller)
  • Linux 系统可视化管理工具
  • python学习打卡day50
  • JPA将大数据量的Excel文件导入到数据库中
  • 拼音字母a和g的写法
  • AI Agent 核心策略解析:Function Calling 与 ReAct 的设计哲学与应用实践
  • React 第五十九节 Router中 createBrowserRouter使用详解与案例分析
  • Etcd数据持久化机制:WAL与Snapshot解析
  • python数据结构和算法(5)
  • zset类型
  • Bright Data网页抓取工具实战:BOSS直聘爬虫 + PandasAI分析洞察前端岗位市场趋势
  • 深度学习小项目合集之图像分割识别-视频介绍下自取