当前位置: 首页 > 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也要再输密码

相关文章:

  • 数据结构(9)排序
  • 成功在 Conda Python 2.7 环境中安装 Clipper(eCLIP peak caller)
  • 01.pycharm整合conda
  • 【数据结构】图论最短路圣器:Floyd算法如何用双矩阵征服负权图?
  • C# TextBox 控件限制输入字符为十六进制字符串
  • 什么是哈希函数
  • AIGC 基础篇 Python基础 05 元组,集合与字典
  • 深入理解 PCIe 协议中 BDF(Bus/Device/Function)分配与管理机制
  • 华为云Flexus+DeepSeek征文 | 基于Dify构建多语言文件翻译工作流
  • Qwen家族系列模型概述
  • 二刷苍穹外卖 day02
  • 强化学习入门:交叉熵方法实现CartPole智能体
  • 【案例实战】轻创业技术手册:如何用最小MVP模型验证市场需求?低成本创业可以做什么?低成本创业项目排行榜前十名!轻资产创业项目做什么比较好?格行代理怎么样?
  • 计算机网络 : 数据链路层
  • Java/Kotlin selenium 无头浏览器 [Headless Chrome] 实现长截图
  • 2025神经网络动力学理论、优化算法及应用专题研讨会 ( NOTAA 2025)
  • 一键生成活动页面的智能体开发实践:从策划到分发的全自动化解决方案
  • Android studio中git突然看不了提交记录
  • flink1.19.2+cdc-3.2.1遇到的问题及解决方案
  • 【AI大模型入门指南】概念与专有名词详解 (二)
  • 大连开发区一中/搜索引擎优化的策略主要有
  • 百度推广自己做网站/收录批量查询
  • 邦拓网站建设/第三方平台推广引流
  • 网站制作需要多少钱客服/福州关键词快速排名
  • 国家企业年审营业执照官网/百度seo排名优化软件
  • 建设银陕西省分行网站/最近一周新闻大事