0


Springboot +JWT实现登录认证,密码加密及Token校验全过程(附源码)

JWT实现登录认证

简介

  • 通俗地说,JWT的本质就是一个字符串,它是将用户信息保存到一个Json字符串中,然后进行编码后得到一个JWT token,并且这个JWT token带有签名信息,接收后可以校验是否被篡改,所以可以用于在各方之间安全地将信息作为Json对象传输。JWT的认证流程如下:
  • 首先,前端通过Web表单将自己的用户名和密码发送到后端的接口,这个过程一般是一个POST请求。建议的方式是通过SSL加密的传输(HTTPS),从而避免敏感信息被嗅探
  • 后端核对用户名和密码成功后,将包含用户信息的数据作为JWT的Payload,将其与JWT Header分别进行Base64编码拼接后签名,形成一个JWT Token,形成的JWT Token就是一个如同lll.zzz.xxx的字符串
  • 后端将JWT Token字符串作为登录成功的结果返回给前端。前端可以将返回的结果保存在浏览器中,退出登录时删除保存的JWT Token即可
  • 前端在每次请求时将JWT Token放入HTTP请求头中的Authorization属性中(解决XSS和XSRF问题)
  • 后端检查前端传过来的JWT Token,验证其有效性,比如检查签名是否正确、是否过期、token的接收方是否是自己等等
  • 验证通过后,后端解析出JWT Token中包含的用户信息,进行其他逻辑操作(一般是根据用户信息得到权限等),返回结果

在这里插入图片描述

本博客项目源码地址:
  • 项目源码github地址
  • 项目源码国内gitee地址

环境

本教程使用jdk11,其他环境自行测试
在这里插入图片描述

api测试工具

postman

{{localhost}}

请自行更改为自己的地址,如

localhost:9999

在这里插入图片描述

1. 依赖

  • 1.1 pom导入依赖
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!--swagger3 生成接口注释--><dependency><groupId>io.springfox</groupId><artifactId>springfox-boot-starter</artifactId><version>3.0.0</version></dependency><!-- 解决jdk11缺失jar包引起的报错--><dependency><groupId>javax.xml.bind</groupId><artifactId>jaxb-api</artifactId></dependency><!-- jwt token核心依赖--><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency><!-- jwt token核心依赖--><dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.18.3</version></dependency>

2. token生成及校验

  • 2.1 封装用户

存储用户的基本信息

importlombok.Data;importlombok.experimental.Accessors;/**
 * @author l
 */@Data@Accessors(chain =true)publicclassJwtUser{privateboolean valid;privateString userId;privateString role;publicJwtUser(){this.valid =false;}}
  • 2.2 编写JWT提供者

主要关注 createToken 和 checkToken 两个方法

  • createToken 生成token
  • checkToken 校验token

createToken

importio.jsonwebtoken.*;importio.swagger.annotations.ApiModel;importio.swagger.annotations.ApiModelProperty;importlombok.extern.slf4j.Slf4j;importjava.nio.charset.StandardCharsets;importjava.util.Base64;importjava.util.Date;/**
 * date: 2021-01-05 08:48
 * description token管理
 *
 * @author qiDing
 */@Slf4j@ApiModel("token提供者")publicclassTokenProvider{@ApiModelProperty("盐")privatestaticfinalString SALT_KEY ="links";@ApiModelProperty("令牌有效期毫秒")privatestaticfinallong TOKEN_VALIDITY =86400000;@ApiModelProperty("权限密钥")privatestaticfinalString AUTHORITIES_KEY ="auth";@ApiModelProperty("Base64 密钥")privatefinalstaticString SECRET_KEY =Base64.getEncoder().encodeToString(SALT_KEY.getBytes(StandardCharsets.UTF_8));/**
     * 生成token
     * @param userId 用户id
     * @param clientId 用于区别客户端,如移动端,网页端,此处可根据自己业务自定义
     * @param role 角色权限
     */publicstaticStringcreateToken(String userId,String clientId,String role){Date validity =newDate((newDate()).getTime()+ TOKEN_VALIDITY);returnJwts.builder()// 代表这个JWT的主体,即它的所有人.setSubject(String.valueOf(userId))// 代表这个JWT的签发主体.setIssuer("")// 是一个时间戳,代表这个JWT的签发时间;.setIssuedAt(newDate())// 代表这个JWT的接收对象.setAudience(clientId).claim("role", role).claim("userId", userId).signWith(SignatureAlgorithm.HS512, SECRET_KEY).setExpiration(validity).compact();}/**
     * 校验token
     */publicstaticJwtUsercheckToken(String token){if(validateToken(token)){Claims claims =Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();String audience = claims.getAudience();String userId = claims.get("userId",String.class);String role = claims.get("role",String.class);JwtUser jwtUser =newJwtUser().setUserId(userId).setRole(role).setValid(true);
            log.info("===token有效{},客户端{}", jwtUser, audience);return jwtUser;}
        log.error("***token无效***");returnnewJwtUser();}privatestaticbooleanvalidateToken(String authToken){try{Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(authToken);returntrue;}catch(Exception e){
            log.error("无效的token:"+ authToken);}returnfalse;}}

3. 登录

  • 3.1 密码加密

对密码进行md5加密

importorg.springframework.util.DigestUtils;/**
 * 密码加密工具类
 *
 * @author liangQiDing
 */publicclassPasswordEncoder{/**
     * 密码加密
     * @param rawPassword 登录时传入的密码
     */publicstaticStringencode(CharSequence rawPassword){returnDigestUtils.md5DigestAsHex(rawPassword.toString().getBytes());}/**
     * 密码对比
     * @param rawPassword 登录时传入的密码
     * @param encodedPassword 数据库保存的加密过的密码
     */publicstaticbooleanmatches(CharSequence rawPassword,String encodedPassword){return encodedPassword.equals(DigestUtils.md5DigestAsHex(rawPassword.toString().getBytes()));}}
  • 3.2 登录接口编写
importcom.example.jwt_dome.config.PasswordEncoder;importcom.example.jwt_dome.jwt.AuthStorage;importcom.example.jwt_dome.jwt.JwtUser;importcom.example.jwt_dome.jwt.TokenProvider;importio.swagger.annotations.Api;importio.swagger.annotations.ApiOperation;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.RestController;importjava.util.HashMap;/**
 * @author liangQiDing
 */@RestController@Api("token测试服务器")publicclassTokenController{/**
     * 模拟数据库数据 账号 admin  密码 123456
     */privatefinalstaticHashMap<String,String> USER =newHashMap<>(){{put("admin","e10adc3949ba59abbe56e057f20f883e");}};@GetMapping("/login")@ApiOperation("登陆示例(账号admin,密码123456)")publicStringlogin(String username,String password){if(PasswordEncoder.matches(password, USER.get(username))){// 模拟一个用户的数据 用户id为1  登录端为网页web  角色是adminreturnTokenProvider.createToken("1","web","admin");}return"error";}@GetMapping("/token/validate")@ApiOperation("token校验")publicJwtUsertokenValidate(String token){returnTokenProvider.checkToken(token);}}
  • 3.3 token获取测试在这里插入图片描述
  • 3.4 token校验测试在这里插入图片描述

4. 编写拦截器进行token校验

  • 4.1 存储授权信息

用于在我们授权通过后,在请求中获取用户的信息

/**
 * 存储本次请求的授权信息,适用于各种业务场景,包括分布式部署
 *
 * @author lqd
 */publicclassAuthStorage{@ApiModelProperty("请求头token的下标")publicstaticfinalString TOKEN_KEY ="token";/**
     * 模拟session
     */privatestaticfinalHashMap<String,JwtUser> JWT_USER =newHashMap<String,JwtUser>();/**
     * 全局获取用户
     */publicstaticJwtUsergetUser(){HttpServletRequest request =((ServletRequestAttributes)Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();return JWT_USER.get(request.getHeader(TOKEN_KEY));}/**
     * 设置用户
     */publicstaticvoidsetUser(String token,JwtUser user){
        JWT_USER.put(token, user);}/**
     * 清除授权
     */publicstaticvoidclearUser(){HttpServletRequest request =((ServletRequestAttributes)Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
        JWT_USER.remove(request.getHeader(TOKEN_KEY));}}
  • 4.2 配置拦截器

在请求响应前,校验token,校验通过后存储用户信息。

importcom.example.jwt_dome.jwt.AuthStorage;importcom.example.jwt_dome.jwt.JwtUser;importcom.example.jwt_dome.jwt.TokenProvider;importlombok.extern.slf4j.Slf4j;importorg.springframework.util.StringUtils;importorg.springframework.web.servlet.HandlerInterceptor;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;/**
 * 拦截器
 *
 * @author lqd
 */@Slf4jpublicclassAuthInterceptorimplementsHandlerInterceptor{@OverridepublicbooleanpreHandle(HttpServletRequest request,HttpServletResponse response,Object handler)throwsException{String token = request.getHeader(AuthStorage.TOKEN_KEY);if(StringUtils.hasLength(token)){JwtUser jwtUser =TokenProvider.checkToken(token);// 是否认证通过if(jwtUser.isValid()){// 保存授权信息AuthStorage.setUser(token, jwtUser);returntrue;}}
        response.setContentType("text/html;charset=utf-8");
        response.getWriter().write("请先登录!");returnfalse;}@OverridepublicvoidafterCompletion(HttpServletRequest request,HttpServletResponse response,Object      handler,Exception ex)throwsException{// 请求完成清除授权信息AuthStorage.clearUser();HandlerInterceptor.super.afterCompletion(request, response, handler, ex);}}
  • 4.3 配置拦截路径
importorg.springframework.context.annotation.Configuration;importorg.springframework.web.servlet.config.annotation.InterceptorRegistry;importorg.springframework.web.servlet.config.annotation.WebMvcConfigurer;/**
 * 配置拦截器路径
 *
 * @author lqd
 */@ConfigurationpublicclassWebMvcConfigimplementsWebMvcConfigurer{@OverridepublicvoidaddInterceptors(InterceptorRegistry registry){
        registry.addInterceptor(newAuthInterceptor())// 拦截的路径.addPathPatterns("/**")// 开放的路径.excludePathPatterns("/login/**","/token/validate");}}
  • 4.4 拦截测试

在controller层添加测试接口

@GetMapping("/get/Info")@ApiOperation("模拟拦截")publicStringgetInfo(){// 从全局环境中获取用户idJwtUser user =AuthStorage.getUser();return"用户:"+user.getUserId()+",请求成功";}

普通访问
在这里插入图片描述

请求头添加token后再访问
在这里插入图片描述

5. 源码下载

  • Springboot开发脚手架,集合各种常用框架使用案例,完善的文档,致力于让开发者快速搭建基础环境并让应用跑起来。
  • 项目源码国内gitee地址
  • 项目源码github地址

本文转载自: https://blog.csdn.net/qq_42411805/article/details/124676309
版权归原作者 全栈小定 所有, 如有侵权,请联系我们删除。

“Springboot +JWT实现登录认证,密码加密及Token校验全过程(附源码)”的评论:

还没有评论