软件开辟创新课程筹划---针对图书商城的登录session缺点改进---基于JWT令牌验证技能 [复制链接]
发表于 2026-3-6 11:20:56 | 显示全部楼层 |阅读模式
写在开头的话

本文章为学校课程【软件开辟与创新】的作业文章,存在不成熟不美满的地方,接待各人一起讨论
阅读本文须要对Java Web开辟、Spring Boot框架有根本的相识
项目选择与分析

学校这学期开了一门软件开辟创新课程,第一节课要求就是针对已有的软件缺陷举行美满大概二次开辟,刚好找到了一个同砚做的图书商城项目,让我们摆设一下试试.



看着还挺不错的,不外我在研究他的登岸体系筹划的时间发现了一个标题:这个项目标会话保持技能是基于session实现的,关于session技能请看这篇文章Session详解,学习Session,这篇文章就够了(包罗底层分析和利用)
技能分析与选择

传统Session登录机制,是基于服务器存储用户会话信息、客户端携带Cookie携带SessionID实现身份校验,在单体小型项目中尚能运行,但放到图书商城这类具备多端访问、可扩展需求的项目中,缺点被无穷放大,详细体现在这几点:

  • 服务器存储压力大,横向扩展受阻
    Session信息默认存储服务器内存中,图书商城面向海量用户,每一位登任命户都会天生独立Session,高并发场景下会大幅占用服务器资源,以致引发内存溢出.而且Session与服务器强绑定,单台服务器宕机,对应用户会话全部失效;若做集群分布式摆设,还需额外搭建Redis、Memcached实现Session共享,大幅增长体系复杂度和运维资本,违背轻量化筹划理念.
  • 跨域与多端适配性极差
    当下图书商城早已不范围于Web端、小步调、APP、H5移动端等多端并行访问已成常态.Session依赖Cookie转达会话标识,而Cookie受同源计谋限定,跨域哀求时无法正常转达,想要实现多端互通,须要繁琐的跨域设置,兼容性差且极易出现登录失效、身份校验失败的标题.
  • 无状态拓展困难,安全性可控性弱
    Session机制属于有状态会话,服务器需连续维护用户登录状态,机动性不敷.同时,SessionID存储在Cookie中,易遭遇CSRF跨站哀求伪造攻击,即便设置Cookie加密、逾期时间,仍然存在安全隐患,难以满足商城体系对用户账号安全的高要求.
针对这些痛点,我们决定摒弃传统的Session技能,选用JWT(JSON Web Token)令牌验证技能,实现无状态、分布式友爱、高适配性的登录验证方案,适配图书商城这类对并发、多端访问有要求的项目标业务需求.
JWT令牌验证技能先容

JWT即JSON Web令牌,是一种轻量级、自包罗、跨语言的身份验证规范,焦点上风在于其具有无状态、分布式友爱、多端兼容、安全性高等特点.
1.JWT根本原理

JWT令牌本质是一段颠末加密署名的JSON字符串,由Header(头部)、Payload(负载)、Signature(署名)三部门构成,三者利用英文句号"."分隔,长成如许:
  1. xxxxx.yyyyy.zzzzz
复制代码

  • Header:声明令牌范例和加密算法,通常采取HS256对称加密算法.
  • Payload:存储用户焦点身份信息(如用户ID、用户名、脚色权限)、令牌签发时间、逾期时间等非敏感数据,但是不发起存储暗码等涉密信息.
  • Signature:通过Header指定的算法,将Header、Payload和密钥加密天生,用于校验令牌是否被篡改,保障安全性.
2.JWT对比Session

对比传统的Session技能,JWT具有以下上风:

  • 无状态存储:服务器无需存储JWT令牌,大幅低沉内存压力,集群摆设时无需做会话共享,恣意服务器均可校验令牌,横向扩展难度低.
  • 跨域多端适配:JWT通过哀求头(Authorization)转达,摆脱Cookie同源计谋限定,Web、小步调、APP等多端通用.
  • 安全性可控:令牌自带署名校验,篡改即失效,可设置机动逾期时间,搭配革新令牌机制,分身安全性与用户利用体验.
  • 轻量高效:令牌体积小,传输速率快,身份校验流程简便,提拔接口相应服从.
项目实战:图书商城JWT登岸验证明现

本项目是基于SpringBoot3技能栈来实现的图书商城后端,JWT登录验证模块的焦点流程分为用户登录签发令牌、哀求拦截校验令牌、令牌逾期设置三步.
1.创建JWT令牌工具类

起首导入全部干系包
  1. import io.jsonwebtoken.Jwts;
  2. import io.jsonwebtoken.SignatureAlgorithm;
  3. import io.jsonwebtoken.security.Keys;
  4. //上面三个包是JWT令牌相关
  5. import org.springframework.context.annotation.Configuration;
  6. import org.springframework.stereotype.Component;
  7. //这两个是SpringBoot的IOC容器配置
  8. import javax.crypto.SecretKey;
  9. import java.util.Date;
  10. import java.util.HashMap;
  11. import java.util.Map;
  12. //这些是加密相关的工具包
复制代码
其次创建JWT工具类界说,请留意,一个简朴的JWT工具类至少应该包罗:

  • 密钥算法
  1. private static final SecretKey SECRET_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS512);
复制代码

  • 令牌逾期时间
  1. private static final long EXPIRATION_TIME = 3600 * 1000;// 1小时
复制代码

  • 天生JWT令牌的方法
  1. /**
  2.      * 生成JWT令牌
  3.      * @param username 用户名
  4.      * @param userId 用户ID
  5.      * @return JWT令牌
  6.      */
  7. // 方法定义:接收用户名和用户ID,返回生成的Token字符串
  8. public String generateToken(String username, String userId) {
  9.     // 1. 创建HashMap存储自定义的JWT声明(Claims)
  10.     Map<String, Object> claims = new HashMap<>();
  11.     // 2. 往声明中添加用户名和用户ID
  12.     claims.put("username", username);
  13.     claims.put("userid", userId);
  14.     // 3. 使用Jwts构建器生成Token
  15.     return Jwts.builder()
  16.             .setSubject(username)  // 设置Token的主题(标准声明)
  17.             .addClaims(claims)     // 添加自定义声明
  18.             .signWith(SECRET_KEY, SignatureAlgorithm.HS512)  // 使用HS512算法和密钥签名
  19.             .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))  // 设置过期时间
  20.             .compact();  // 压缩生成最终的Token字符串
  21. }
  22. //我这里封装了用户名和id信息,请根据实际需要选择封装信息,但是请不要封装密码等敏感信息!!!请不要封装密码等敏感信息!!!请不要封装密码等敏感信息!!!
复制代码

  • 令牌分析方法
  1. /**
  2.      * 解析JWT令牌并获取用户信息
  3.      * @param token JWT令牌
  4.      * @return 用户信息Map
  5.      */
  6. public Map<String, Object> parseToken(String token) {
  7.     try {
  8.         // 1. 构建JWT解析器,设置签名密钥并解析token
  9.         var claims = Jwts.parserBuilder()
  10.                 .setSigningKey(SECRET_KEY)  // 设置验证签名的密钥
  11.                 .build()                    // 构建解析器
  12.                 .parseClaimsJws(token);     // 解析token并验证签名
  13.         // 2. 从解析结果中提取用户信息,封装到HashMap
  14.         Map<String, Object> userInfo = new HashMap<>();
  15.         userInfo.put("username", claims.getBody().get("username")); // 提取用户名
  16.         userInfo.put("userid", claims.getBody().get("userid"));     // 提取用户ID
  17.         userInfo.put("exp", claims.getBody().getExpiration());      // 提取过期时间
  18.         System.out.println(userInfo);                               // 打印用户信息
  19.         return userInfo;                                            // 返回用户信息Map
  20.     } catch (Exception e) {
  21.         // 3. 捕获所有异常,封装成运行时异常抛出
  22.         throw new RuntimeException("JWT令牌解析失败: " + e.getMessage());
  23.     }
  24. }
复制代码

  • 令牌验证方法
  1. /**
  2.      * 验JWT令牌是否有效
  3.      * @param token JWT令牌
  4.      * @return 是否有效
  5.      */
  6. public boolean validateToken(String token) {
  7.     try {
  8.         // 1. 创建JWT解析器构建器
  9.         Jwts.parserBuilder()
  10.                 // 2. 设置签名密钥(用于验证token的签名是否被篡改)
  11.                 .setSigningKey(SECRET_KEY)
  12.                 // 3. 构建解析器实例
  13.                 .build()
  14.                 // 4. 解析token并验证:
  15.                 //    - 验证签名是否正确
  16.                 //    - 检查token是否过期(默认校验exp声明)
  17.                 //    - 检查token格式是否合法
  18.                 .parseClaimsJws(token);
  19.         // 5. 解析成功=token合法,返回true
  20.         return true;
  21.     } catch (Exception e) {
  22.         // 6. 任何异常(签名错误、过期、格式错误等)都返回false
  23.         return false;
  24.     }
  25. }
复制代码
完备代码如下
  1. import io.jsonwebtoken.Jwts;
  2. import io.jsonwebtoken.SignatureAlgorithm;
  3. import io.jsonwebtoken.security.Keys;
  4. import org.springframework.context.annotation.Configuration;
  5. import org.springframework.stereotype.Component;
  6. import javax.crypto.SecretKey;
  7. import java.util.Date;
  8. import java.util.HashMap;
  9. import java.util.Map;
  10. /**
  11. * JWT工具类,用于生成和解析JWT令牌
  12. */
  13. @Component
  14. @Configuration
  15. public class JwtUtils {
  16.     // 默认密钥
  17.     private static final SecretKey SECRET_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS512);
  18.     // 令牌过期时间
  19.     private static final long EXPIRATION_TIME = 3600 * 1000; // 1小时
  20.     /**
  21.      * 生成JWT令牌
  22.      * @param username 用户名
  23.      * @param userId 用户ID
  24.      * @return JWT令牌
  25.      */
  26.     public String generateToken(String username, String userId) {
  27.         Map<String, Object> claims = new HashMap<>();
  28.         claims.put("username", username);
  29.         claims.put("userid", userId);
  30.         return Jwts.builder()
  31.                 .setSubject(username)
  32.                 .addClaims(claims)
  33.                 .signWith(SECRET_KEY, SignatureAlgorithm.HS512)
  34.                 .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
  35.                 .compact();
  36.     }
  37.     /**
  38.      * 解析JWT令牌并获取用户信息
  39.      * @param token JWT令牌
  40.      * @return 用户信息Map
  41.      */
  42.     public Map<String, Object> parseToken(String token) {
  43.         try {
  44.             var claims = Jwts.parserBuilder()
  45.                     .setSigningKey(SECRET_KEY)
  46.                     .build()
  47.                     .parseClaimsJws(token);
  48.             Map<String, Object> userInfo = new HashMap<>();
  49.             userInfo.put("username", claims.getBody().get("username"));
  50.             userInfo.put("userid", claims.getBody().get("userid"));
  51.             userInfo.put("exp", claims.getBody().getExpiration());
  52.             System.out.println(userInfo);
  53.             return userInfo;
  54.         } catch (Exception e) {
  55.             throw new RuntimeException("JWT令牌解析失败: " + e.getMessage());
  56.         }
  57.     }
  58.     /**
  59.      * 验JWT令牌是否有效
  60.      * @param token JWT令牌
  61.      * @return 是否有效
  62.      */
  63.     public boolean validateToken(String token) {
  64.         try {
  65.             Jwts.parserBuilder()
  66.                     .setSigningKey(SECRET_KEY)
  67.                     .build()
  68.                     .parseClaimsJws(token);
  69.             return true;
  70.         } catch (Exception e) {
  71.             return false;
  72.         }
  73.     }
  74. }
复制代码
在完成工具类的开辟之后,我们还须要一个JWT认证拦截器,用于拦截哀求,对图书选购,购物车检察,下单流程,个人信息检察等敏感接口哀求举行拦截,只有携带正当令牌的哀求才气访问.
拦截器代码如下:
  1. /**
  2. * JWT认证拦截器
  3. */
  4. @Component
  5. public class JwtInterceptor implements HandlerInterceptor {
  6.     @Autowired
  7.     private JwtUtils jwtUtils;
  8.     @Autowired
  9.     private ObjectMapper objectMapper;
  10.     @Override
  11.     public boolean preHandle( HttpServletRequest request,  HttpServletResponse response, Object handler) throws Exception {
  12.         String path = request.getRequestURI();
  13.         System.out.println("当前请求路径:" + path);
  14.         // 检查是否命中排除规则
  15.         boolean isExcluded = path.startsWith("/img/") || path.startsWith("/static/") ;
  16.         System.out.println("是否排除:" + isExcluded);
  17.         if (isExcluded) {
  18.             return true; // 排除的路径直接放行
  19.         }
  20.         // 在 preHandle 方法中提前放行静态资源
  21.         if (request.getRequestURI().startsWith("/img/")) {
  22.             return true;
  23.         }
  24.         //检查是否为登录请求,如果是则放行
  25.         if (request.getRequestURI().contains("/login")) {
  26.             System.out.println("登录请求");
  27.             return true;
  28.         }
  29.         //检查是否是注册请求,如果是则放行
  30.         if (request.getRequestURI().contains("/register")) {
  31.             System.out.println("注册请求");
  32.             return true;
  33.         }
  34.         //检查是否是商品信息获取请求,如果是则放行
  35.         if (request.getRequestURI().contains("/goodsInfo")) {
  36.             System.out.println("商品信息获取请求");
  37.             return true;
  38.         }
  39.         // 获取请求头中的token
  40.         String token = request.getHeader("Authorization");
  41.         // 如果请求头中没有token,则响应401错误
  42.         if (token == null) {
  43.             response.setContentType("application/json;charset=UTF-8");
  44.             response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
  45.             String resultJson = objectMapper.writeValueAsString(Result.error(401,"请先登录"));
  46.             response.getWriter().write(resultJson);
  47.             return false;
  48.         }
  49.         System.out.println(token);
  50.         // 验证token是否有效
  51.         if (!jwtUtils.validateToken(token)) {
  52.             response.setContentType("application/json;charset=UTF-8");
  53.             response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
  54.             String resultJson = objectMapper.writeValueAsString(Result.error(401,"token无效"));
  55.             response.getWriter().write(resultJson);
  56.             return false;
  57.         }
  58.         // 验证通过,继续执行请求
  59.         return true;
  60.     }
  61. }
复制代码
软件测试

在颠末一顿大刀阔斧的改动之后,我们就可以来测试一下新的登岸体系
测试用比方下

部门测试效果如下:


后续还须要做回归测试,验证其他的功能有没有受到影响,不外限于本文篇幅,就不详细的誊写了
思考与总结

本次针对图书商城项目登录模块的改造,从传统Session机制迁徙至JWT令牌验证体系,不光办理了原项目在分布式摆设、多端访问、服务器性能等方面的焦点痛点,也让我对Java Web开辟中身份认证技能的选型与落地有了更深刻的明白,现将整个过程的思考与劳绩总结如下:
一、技能改造的焦点劳绩


  • 感受到了"无状态"架构的实用性
    改造前对"无状态"的认知仅停顿在理论层面,实操后才真正领会到:JWT将用户身份信息封装在令牌中,服务器无需存储会话数据,不光低沉了内存占用,更让项目具备了横向扩展的底子——只需多摆设几台应用服务器,无需额外搭建Session共享中央件,就能支持更高的并发量,这对商城类面向海量用户的项目至关告急。
  • 意识到技能选型应该均衡安全性与易用性
    开辟过程中深刻意识到JWT并非"万能钥匙":固然摆脱了Cookie同源计谋的限定,适配了Web、小步调等多端场景,但也存在令牌一旦签发无法主动取消的标题。为此我在筹划中强化了两点:一是严酷控制Payload仅存储用户名、用户ID等非敏感信息,杜绝暗码、手机号等数据泄漏风险;二是将令牌逾期时间设为1小时,既制止频仍登录影响用户体验,也低沉了令牌被盗用后的风险窗口。
  • 拦截器筹划的界限思考
    编写JWT拦截器时,我踩过"过分拦截"的坑:最初未区分静态资源(图片、CSS)和业务接口,导致页面样式加载失败。因此拦截器筹划必须明白"拦截范围"——仅对购物车、下单、个人信息等敏感业务接口做令牌校验,对登录、注册、商品展示等公开接口及静态资源直接放行,才气包管体系功能的完备性。
二、现存标题与优化方向


  • 缺少革新令牌机制
    当前令牌逾期后用户需重新登录,体验较差。后续可引入"双令牌"机制:签发Access Token(短期有用,1小时)和Refresh Token(长期有用,7天),当Access Token逾期时,前端用未逾期的Refresh Token哀求新的Access Token,既包管安全性,又提拔用户体验。
  • 令牌校验逻辑存在优化空间
    现有validateToken方法仅校验署名和逾期时间,未对用户状态做校验(如用户账号被封禁后,令牌仍大概有用)。后续可团结Redis维护"无效令牌黑名单",将封禁用户、主动登出的令牌存入黑名单,在校验时增长"令牌是否在黑名单"的判定,增补JWT无法主动取消的缺陷。
三、对软件开辟创新的明白

本次改造并非从零开辟新功能,而是针对已有项目标缺陷举行"精准优化",这让我熟悉到:软件开辟的创新未必是技能的颠覆,更多是团结业务场景的"符合选型"与"细节打磨"。原项目利用Session在单体测试环境中能正常运行,但未思量商城项目标实际利用场景(多端、高并发),而我的改造焦点就是让技能方案匹配业务需求——这正是"软件开辟与创新"课程的焦点要义:技能服务于业务,而非为了用技能而用技能
四、总结

通过本次改造,我不光把握了JWT的焦点用法(令牌天生、分析、校验)、Spring Boot拦截器的开辟,更告急的是创建了"从业务痛点出发选择技能方案"的头脑方式。后续我会连续美满该项目:增补革新令牌、黑名单校验、密钥规范化管理等功能,同时完玉成量回归测试,确保改造后的登录模块既安全可靠,又能兼容原项目标全部业务流程。
这次实践也让我明白,良好的软件体系不是一挥而就的,而是在连续发现标题、办理标题标过程中迭代优化的——小到一个拦截器的放行规则,大到整个认证体系的选型,每一个细节的打磨,都是软件开辟创新的表现。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表