2026/4/17 12:09:30
网站建设
项目流程
包头市网站建设,推广网站排名优化seo教程,做收费网站,怎么做自已的网站文章目录参数校验#xff1a;jakarta.validation常见注解使用实例如何触发验证#xff1f;JWT1. 传统登录方式的问题2. JWT令牌技术解决方案令牌技术优点JWT介绍JWT组成3. 实现JWT登录认证3.1 添加JWT依赖3.2 创建JWT工具类3.3 创建配置类3.4 前端实现的细节4. Auth0 提供的 …文章目录参数校验jakarta.validation常见注解使用实例如何触发验证JWT1. 传统登录方式的问题2. JWT令牌技术解决方案令牌技术优点JWT介绍JWT组成3. 实现JWT登录认证3.1 添加JWT依赖3.2 创建JWT工具类3.3 创建配置类3.4 前端实现的细节4. Auth0 提供的 JWT加密/加盐1. 密码加密方案2. 实现加密工具类3. 修改登录验证逻辑参数校验jakarta.validationJakarta Bean Validation提供了一种基于注解的方式来验证 Java 对象中的属性是否符合规则通常用于表单输入校验Web 开发DTO 参数校验SpringMVC、Jakarta REST持久化数据校验JPASpringBoot 项目使用时添加以下依赖即可dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-validation/artifactId/dependency常见注解注解含义NotNull字段不能为 nullNotBlank字符串不为 null 且去除空格后长度大于0NotEmpty集合、数组或字符串不为 null 且不为空Size(min, max)长度或元素个数在一定范围内Min(value)最小值适用于数字Max(value)最大值适用于数字Email字符串必须为邮箱格式Pattern(regexp)正则表达式匹配Past / Future时间必须是过去/未来使用实例publicclassUser{NotBlank(message用户名不能为空)privateStringusername;Email(message邮箱格式不正确)privateStringemail;Size(min6,message密码长度不能少于6位)privateStringpassword;Min(value18,message年龄不能小于18岁)privateIntegerage;// getters and setters}如何触发验证在Spring框架中配合Valid或Validated注解使用PostMapping(/register)publicResponseEntity?register(ValidRequestBodyUseruser,BindingResultresult){if(result.hasErrors()){returnResponseEntity.badRequest().body(result.getAllErrors());}returnResponseEntity.ok(注册成功);}JWT1. 传统登录方式的问题传统的登录认证流程通常是用户提交用户名密码到服务器服务器验证身份并创建Session服务器通过Cookie返回sessionId给浏览器但在集群环境下这种方式存在问题单点故障风险高多服务器环境下一个用户的请求可能被分发到不同服务器第一台服务器创建的Session在第二台服务器上不存在导致用户需要重复登录2. JWT令牌技术解决方案令牌其实就是一个用户身份的标识本质就是一个字符串。服务器只需要存放一份密钥来判断token中payload部分是否发生变化有点像证书机制而不需要像session机制那样存放大量的session字符串大大节省存储空间~令牌技术优点解决了集群环境下的认证问题减轻服务器的存储压力(无需在服务器端存储)JWT介绍JWT全称JSON Web Token官网https://jwt.io/是一种紧凑的URL安全方法用于客户端和服务器之间传递安全可靠的信息JWT组成JWT 由三部分组成每部分中间使用 . 分隔如aaaaa.bbbbb.ccccHeader头部包括令牌类型及使用的哈希算法Payload载荷存放有效信息的地方如用户ID、用户名、过期时间戳等Signature签名将头部载荷结合密钥进行加密用于防止JWT内容被篡改。JWT 解决的是 “信任” 问题而不是 “隐私” 问题即 JWT 并没有办法保证数据内容的安全性所以不要在载荷中存放敏感信息所有部分使用Base64Url编码注意Base64是编码方式不是加密方式3. 实现JWT登录认证3.1 添加JWT依赖dependencygroupIdio.jsonwebtoken/groupIdartifactIdjjwt-api/artifactIdversion0.11.5/version/dependencydependencygroupIdio.jsonwebtoken/groupIdartifactIdjjwt-impl/artifactIdversion0.11.5/versionscoperuntime/scope/dependencydependencygroupIdio.jsonwebtoken/groupIdartifactIdjjwt-jackson/artifactIdversion0.11.5/versionscoperuntime/scope/dependency然后在配置文件中添加密钥这里采用配置文件引入的方式jwt.secretwFApjmSTFmWZZix27k/w5ltH3YK9u3/e01IdCNsZ4Jk3.2 创建JWT工具类Slf4jpublicclassJwtUtil{// 没办法直接调用非静态变量secret// 所以换个思路用传参方式来进行初始化// 即创建配置类调用init()来进行SECRET_KEY的初始化privatestaticKeySECRET_KEY;// 由配置类主动调用初始化对secret进行解码然后转化为Key类型publicstaticvoidinit(Stringsecret){log.info(初始化密钥{},secret);SECRET_KEYKeys.hmacShaKeyFor(Decoders.BASE64.decode(secret));}/** * 根据传入的claims也就是载荷生成对应的JWT */publicstaticStringcreateJWT(MapString,Objectclaims){if(SECRET_KEYnull){thrownewIllegalStateException(SECRET_KEY 未初始化);}Stringjwtnull;try{jwtJwts.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_KEYnull){thrownewIllegalStateException(SECRET_KEY 未初始化);}if(!StringUtils.hasText(jwt)){thrownewIllegalArgumentException(JWT参数错误);}Claimsclaimnull;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密钥的。Slf4jComponentpublicclassJWTConfig{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.code200result.data!){varresponseresult.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_tokenlocalStorage.getItem(user_token);xhr.setRequestHeader(user_token,user_token);});4. Auth0 提供的 JWTJWT是 Auth0 提供的库类包名是com.auth0.jwt而前面的Jwts是 JJWT 库的工具类包名是io.jsonwebtoken。这个包实现 JWT 会更加简洁一些先引入依赖dependencygroupIdcom.auth0/groupIdartifactIdjava-jwt/artifactIdversion4.x.x/version/dependency然后编写工具类/** * JWT 工具类 */publicclassJwtUtil{// 密钥privatestaticfinalStringSECRET_KEYxxx;// 更改为你的密钥// 设置 JWT 的过期时间 6 小时privatestaticfinallongEXPIRATION_TIME1000*60*60*6;/** * 生成 JWT token * * param claims 自定义的业务数据 * return JWT token */publicstaticStringgenerateToken(MapString,Objectclaims){returnJWT.create().withClaim(claims,claims)// 自定义的业务数据.withExpiresAt(newDate(System.currentTimeMillis()EXPIRATION_TIME))// 设置过期时间.sign(Algorithm.HMAC256(SECRET_KEY));// 使用 HMAC256 算法加密}/** * 解析 JWT token * * param token JWT token * return 自定义的业务数据 */publicstaticMapString,ObjectparseToken(Stringtoken){returnJWT.require(Algorithm.HMAC256(SECRET_KEY)).build().verify(token).getClaim(claims).asMap();}}加密/加盐1. 密码加密方案博客系统中采用MD5算法 盐值进行密码加密使用随机字符串作为 “盐”将盐与密码组合后进行MD5加密存储格式为盐值 MD5盐值密码其中 “盐值” 是指一个随机字符串。2. 实现加密工具类publicclassSecureUtils{publicstaticStringencrypt(Stringpasswd){// 1. 获取盐值StringsaltUUID.randomUUID().toString().replace(-,);// 2. 获取 盐值密码 进行md5加密后的密文StringretDigestUtils.md5DigestAsHex((saltpasswd).getBytes(StandardCharsets.UTF_8));// 3. 返回真正的密码是盐值 加密后的密文returnsaltret;}publicstaticBooleanisValidated(Stringciphertext,Stringpasswd){if(!StringUtils.hasLength(passwd)||!StringUtils.hasLength(ciphertext)){returnfalse;}if(ciphertext.length()!64){returnfalse;}Stringsaltciphertext.substring(0,32);// 拿到盐值StringtmpDigestUtils.md5DigestAsHex((saltpasswd).getBytes(StandardCharsets.UTF_8));return(salttmp).equals(ciphertext);}}3. 修改登录验证逻辑// login...// 走到这说明用户存在则进行密码判断if(SecureUtils.isValidated(userInfo.getPassword(),password)){// 验证成功...}else{thrownewBlogException(密码不正确);}