news 2026/7/14 16:49:08

【SpringBoot】validation参数校验 JWT鉴权实现 加密/加盐

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【SpringBoot】validation参数校验 JWT鉴权实现 加密/加盐

文章目录

  • 参数校验:`jakarta.validation`
    • 常见注解
    • 使用实例
    • 如何触发验证?
  • JWT
    • 1. 传统登录方式的问题
    • 2. JWT令牌技术解决方案
      • 令牌技术优点
      • JWT介绍
      • JWT组成
    • 3. 实现JWT登录认证
      • 3.1 添加JWT依赖
      • 3.2 创建JWT工具类
      • 3.3 创建配置类
      • 3.4 前端实现的细节
    • 4. Auth0 提供的 JWT
  • 加密/加盐
    • 1. 密码加密方案
    • 2. 实现加密工具类
    • 3. 修改登录验证逻辑

参数校验:jakarta.validation

Jakarta Bean Validation提供了一种基于注解的方式来验证 Java 对象中的属性是否符合规则,通常用于:

  • 表单输入校验(Web 开发)
  • DTO 参数校验(SpringMVC、Jakarta REST)
  • 持久化数据校验(JPA)

SpringBoot 项目使用时,添加以下依赖即可:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>

常见注解

注解含义
@NotNull字段不能为 null
@NotBlank字符串不为 null 且去除空格后长度大于0
@NotEmpty集合、数组或字符串不为 null 且不为空
@Size(min, max)长度或元素个数在一定范围内
@Min(value)最小值(适用于数字)
@Max(value)最大值(适用于数字)
@Email字符串必须为邮箱格式
@Pattern(regexp)正则表达式匹配
@Past / @Future时间必须是过去/未来

使用实例

publicclassUser{@NotBlank(message="用户名不能为空")privateStringusername;@Email(message="邮箱格式不正确")privateStringemail;@Size(min=6,message="密码长度不能少于6位")privateStringpassword;@Min(value=18,message="年龄不能小于18岁")privateIntegerage;// getters and setters}

如何触发验证?

Spring框架中,配合@Valid@Validated注解使用:

@PostMapping("/register")publicResponseEntity<?>register(@Valid@RequestBodyUseruser,BindingResultresult){if(result.hasErrors()){returnResponseEntity.badRequest().body(result.getAllErrors());}returnResponseEntity.ok("注册成功");}

JWT

1. 传统登录方式的问题

传统的登录认证流程通常是:

  • 用户提交用户名密码到服务器
  • 服务器验证身份并创建Session
  • 服务器通过Cookie返回sessionId给浏览器

但在集群环境下,这种方式存在问题:

  • 单点故障风险高
  • 多服务器环境下,一个用户的请求可能被分发到不同服务器
  • 第一台服务器创建的Session在第二台服务器上不存在,导致用户需要重复登录

2. JWT令牌技术解决方案

令牌其实就是一个用户身份的标识,本质就是一个字符串

服务器只需要存放一份密钥来判断tokenpayload部分是否发生变化(有点像证书机制),而不需要像session机制那样存放大量的session字符串,大大节省存储空间~

令牌技术优点

  • 解决了集群环境下的认证问题
  • 减轻服务器的存储压力(无需在服务器端存储)

JWT介绍

  • JWT全称:JSON Web Token
  • 官网:https://jwt.io/
  • 是一种紧凑的URL安全方法,用于客户端和服务器之间传递安全可靠的信息

JWT组成

JWT` 由三部分组成,每部分中间使用 . 分隔,如:aaaaa.bbbbb.cccc
  1. Header(头部):包括令牌类型及使用的哈希算法
  2. Payload(载荷):存放有效信息的地方,如用户ID、用户名、过期时间戳等
  3. Signature(签名):将头部+载荷结合密钥进行加密,用于防止JWT内容被篡改。
    1. JWT 解决的是 “信任” 问题,而不是 “隐私” 问题,即 JWT 并没有办法保证数据内容的安全性,所以不要在载荷中存放敏感信息

所有部分使用Base64Url编码(注意:Base64是编码方式,不是加密方式)

3. 实现JWT登录认证

3.1 添加JWT依赖

<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-api</artifactId><version>0.11.5</version></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-impl</artifactId><version>0.11.5</version><scope>runtime</scope></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId><version>0.11.5</version><scope>runtime</scope></dependency>

然后在配置文件中添加密钥(这里采用配置文件引入的方式)

jwt.secret=wFApjmSTFmWZZix27k/w5ltH3YK9u3/e01IdCNsZ4Jk=

3.2 创建JWT工具类

@Slf4jpublicclassJwtUtil{// 没办法直接调用非静态变量secret// 所以换个思路,用传参方式来进行初始化// 即创建配置类调用init()来进行SECRET_KEY的初始化privatestaticKeySECRET_KEY;// 由配置类主动调用初始化,对secret进行解码,然后转化为Key类型publicstaticvoidinit(Stringsecret){log.info("初始化密钥:{}",secret);SECRET_KEY=Keys.hmacShaKeyFor(Decoders.BASE64.decode(secret));}/** * 根据传入的claims也就是载荷,生成对应的JWT */publicstaticStringcreateJWT(Map<String,Object>claims){if(SECRET_KEY==null){thrownewIllegalStateException("SECRET_KEY 未初始化!");}Stringjwt=null;try{jwt=Jwts.builder().setClaims(claims).signWith(SECRET_KEY,io.jsonwebtoken.SignatureAlgorithm.HS256).setIssuedAt(newDate()).setExpiration(newDate(System.currentTimeMillis()+Constants.TOKEN_EXPIRE_TIME))// 1小时有效.compact();// 👈 核心!将 header + payload + signature 拼接、压缩、编码成一个标准的 JWT 字符串。}catch(Exceptione){thrownewJwtException("创建令牌出错",e);}returnjwt;}/** * 将生成JWT字符串解析后进行返回 */publicstaticClaimsparseJWT(Stringjwt){if(SECRET_KEY==null){thrownewIllegalStateException("SECRET_KEY 未初始化!");}if(!StringUtils.hasText(jwt)){thrownewIllegalArgumentException("JWT参数错误!");}Claimsclaim=null;try{claim=(Claims)Jwts.parserBuilder().setSigningKey(SECRET_KEY).build().parseClaimsJws(jwt).getBody();// ✅ 检查是否过期if(claim.getExpiration().before(newDate())){thrownewRuntimeException("Token 已过期");}returnclaim;}catch(ExpiredJwtExceptione){thrownewJwtException("Token 已过期",e);}catch(JwtExceptione){thrownewJwtException("Token 非法",e);}catch(Exceptione){thrownewJwtException("解析令牌出错",e);}}}

3.3 创建配置类

这个配置类就是用于初始化上面JWTUtils中的SECRET_KEY密钥的。

@Slf4j@ComponentpublicclassJWTConfig{@Value("${jwt.secret}")privateStringsecret;// 不能为static,否则注入不成功,直接为null//该方法在注入secret后才执行@PostConstructpublicvoidinit(){log.info("【JWTUtils】PostConstruct 正在执行...");JWTUtils.init(secret);// 调用工具类的初始化方法}}

3.4 前端实现的细节

前端想在页面跳转后还能用token进行验证,那么就得用localStorage.setItem()进行存储,然后需要用到的时候就用localStorage.getItem()获取即可!

functionlogin(){$.ajax({type:"post",url:"/user/login",contentType:"application/json",data:JSON.stringify({"userName":$("#username").val(),"password":$("#password").val()}),success:function(result){if(result.code==200&&result.data!=""){varresponse=result.data;localStorage.setItem("user_token",response.token);localStorage.setItem("loginUserId",response.userId);location.assign("blog_list.html");}else{alert("用户名或密码错误");return;}}});}

然后在前端统一处理部分,每次访问新页面的时候,就设置请求,发送该token给后端进行校验,如下所示:

$(document).ajaxSend(function(e,xhr,opt){varuser_token=localStorage.getItem("user_token");xhr.setRequestHeader("user_token",user_token);});

4. Auth0 提供的 JWT

JWT是 Auth0 提供的库类(包名是com.auth0.jwt),而前面的Jwts是 JJWT 库的工具类(包名是io.jsonwebtoken)。

这个包实现 JWT 会更加简洁一些

先引入依赖:

<dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>4.x.x</version></dependency>

然后编写工具类

/** * JWT 工具类 */publicclassJwtUtil{// 密钥privatestaticfinalStringSECRET_KEY="xxx";// 更改为你的密钥// 设置 JWT 的过期时间 6 小时privatestaticfinallongEXPIRATION_TIME=1000*60*60*6;/** * 生成 JWT token * * @param claims 自定义的业务数据 * @return JWT token */publicstaticStringgenerateToken(Map<String,Object>claims){returnJWT.create().withClaim("claims",claims)// 自定义的业务数据.withExpiresAt(newDate(System.currentTimeMillis()+EXPIRATION_TIME))// 设置过期时间.sign(Algorithm.HMAC256(SECRET_KEY));// 使用 HMAC256 算法加密}/** * 解析 JWT token * * @param token JWT token * @return 自定义的业务数据 */publicstaticMap<String,Object>parseToken(Stringtoken){returnJWT.require(Algorithm.HMAC256(SECRET_KEY)).build().verify(token).getClaim("claims").asMap();}}

加密/加盐

1. 密码加密方案

博客系统中采用MD5算法 + 盐值进行密码加密:

  • 使用随机字符串作为 “盐”
  • 将盐与密码组合后进行MD5加密
  • 存储格式为:盐值 + MD5(盐值+密码)

其中 “盐值” 是指一个随机字符串。

2. 实现加密工具类

publicclassSecureUtils{publicstaticStringencrypt(Stringpasswd){// 1. 获取盐值Stringsalt=UUID.randomUUID().toString().replace("-","");// 2. 获取 "盐值+密码" 进行md5加密后的密文Stringret=DigestUtils.md5DigestAsHex((salt+passwd).getBytes(StandardCharsets.UTF_8));// 3. 返回真正的密码是:盐值 + 加密后的密文returnsalt+ret;}publicstaticBooleanisValidated(Stringciphertext,Stringpasswd){if(!StringUtils.hasLength(passwd)||!StringUtils.hasLength(ciphertext)){returnfalse;}if(ciphertext.length()!=64){returnfalse;}Stringsalt=ciphertext.substring(0,32);// 拿到盐值Stringtmp=DigestUtils.md5DigestAsHex((salt+passwd).getBytes(StandardCharsets.UTF_8));return(salt+tmp).equals(ciphertext);}}

3. 修改登录验证逻辑

// login...// 走到这说明用户存在,则进行密码判断if(SecureUtils.isValidated(userInfo.getPassword(),password)){// 验证成功...}else{thrownewBlogException("密码不正确");}

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/13 18:30:36

LUT调色包应用场景匹配:根据画面内容推荐最佳配色

LUT调色包应用场景匹配&#xff1a;根据画面内容推荐最佳配色 在影视后期、广告制作和数字内容创作中&#xff0c;调色从来不只是“让画面更好看”这么简单。它承载着情绪表达、风格定义甚至品牌识别的重任。然而&#xff0c;一个资深调色师花十分钟试错五个LUT&#xff08;查…

作者头像 李华
网站建设 2026/7/3 1:30:03

/root/yichuidingyin.sh脚本详解:自动化部署的核心逻辑

/root/yichuidingyin.sh 脚本详解&#xff1a;自动化部署的核心逻辑 在大模型技术飞速演进的今天&#xff0c;一个70亿参数的语言模型已经不再稀奇——真正让人头疼的是&#xff0c;如何在有限时间内把这样一个庞然大物从下载、训练到上线服务完整跑通。传统流程中&#xff0c;…

作者头像 李华
网站建设 2026/7/8 19:55:49

PyCharm插件市场新增AI助手:代码补全与错误修复一体化

PyCharm插件市场新增AI助手&#xff1a;代码补全与错误修复一体化 在今天的Python开发环境中&#xff0c;一个新趋势正悄然改变开发者的工作流——越来越多的AI编程助手开始出现在PyCharm的插件市场中。这些插件不再只是简单的语法提示工具&#xff0c;而是能够理解上下文、自动…

作者头像 李华
网站建设 2026/7/13 0:13:31

CDN加速服务接入:全球多地节点确保图片上传下载流畅

CDN加速服务接入&#xff1a;全球多地节点确保图片上传下载流畅 在数字内容呈指数级增长的今天&#xff0c;一张泛黄的老照片可能承载着几代人的记忆。无论是家庭相册中的黑白影像&#xff0c;还是城市建筑的历史档案&#xff0c;如何让这些珍贵的画面“活”起来&#xff0c;成…

作者头像 李华
网站建设 2026/7/1 18:17:34

C17标准更新后如何确保代码兼容?:3大关键测试策略一文讲透

第一章&#xff1a;C17标准的核心变更与兼容性挑战C17&#xff08;也称为C18&#xff09;作为ISO/IEC 9899:2018标准的通用名称&#xff0c;是C语言继C11之后的修订版本&#xff0c;主要聚焦于错误修复和缺陷澄清&#xff0c;而非引入大规模新特性。尽管其变更幅度较小&#xf…

作者头像 李华
网站建设 2026/7/2 2:36:57

高校科研团队适用:教育场景下的免费算力申请通道

高校科研团队适用&#xff1a;教育场景下的免费算力申请通道 在人工智能研究的浪潮中&#xff0c;越来越多高校团队希望投身大模型与多模态系统的探索。然而现实却常常令人望而却步——动辄数十GB显存的训练需求、复杂的分布式配置、漫长的模型下载过程&#xff0c;再加上高昂…

作者头像 李华