使用SpringBoot+JWT+MybatisPlus实现简单的登陆状态验证

- 日理万妓 2024-03-25 22:45 146阅读 0赞

文章目录

  • 前言
  • 一、JWT是什么?
  • 二、使用步骤
    • 1.创建项目,导入依赖,配置、引入工具类
    • 2.编写LoginController和UserController
    • 3.编写跨域拦截器和token验证拦截器
    • 4.全局拦截器配置
  • 三、业务逻辑
  • 四、测试
  • 总结

前言

登陆功能是每个系统的最基本功能,在SSM技术栈中,登陆状态验证一般会使用服务端的session,但是session并没有想象中的那么好用,经常会出现由于sessionid不一致导致的信息丢失,更好的解决方案就是使用JWT的Token生成。


一、JWT是什么?

JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息,该信息可以被验证和信任,因为它是数字签名的,常用于单点登录。

JWT-token
JSON Web Token由三部分组成,它们之间用圆点(.)连接。这三部分分别是:头部,载荷,签名

  • Header
    头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。这也可以被表示成一个JSON对象,示例:{“typ”:“JWT”,“alg”:“HS256”}
    在头部指明了签名算法是HS256算法。 我们可以通过BASE64进行编码/解码:https://www.matools.com/base64/
  • Payload
    载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分。
    1.标准中注册的声明(建议但不强制使用):例如:sub表示jwt所面向的用户
    2.公共的声明:可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.
    3.私有的声明:私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。
    例如:定义一个payload:
    {“sub”:“1234567890”,“name”:“John Doe”,“admin”:true}
    然后将其进行base64加密,得到Jwt的第二部分:eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
  • Signature
    jwt的第三部分是一个签证信息,这个签证信息由三部分组成:header + payload + secret
    这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。
    注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了

本次使用jwt的token生成来实现一个登陆状态的验证。

二、使用步骤

1.创建项目,导入依赖,配置、引入工具类

本次使用mybatis-plus的一键生成项目,具体的步骤可以查看我之前的文章mybatis-plus详解http://t.csdn.cn/F0QcR
引入依赖

  1. <!-- jwt -->
  2. <dependency>
  3. <groupId>io.jsonwebtoken</groupId>
  4. <artifactId>jjwt-impl</artifactId>
  5. <version>0.11.2</version>
  6. <scope>runtime</scope>
  7. </dependency>
  8. <dependency>
  9. <groupId>io.jsonwebtoken</groupId>
  10. <artifactId>jjwt-api</artifactId>
  11. <version>0.11.2</version>
  12. </dependency>
  13. <dependency>
  14. <groupId>io.jsonwebtoken</groupId>
  15. <artifactId>jjwt-jackson</artifactId>
  16. <version>0.11.2</version>
  17. <scope>runtime</scope>
  18. </dependency>
  19. <dependency>
  20. <groupId>org.apache.commons</groupId>
  21. <artifactId>commons-pool2</artifactId>
  22. <version>2.7.0</version>
  23. </dependency>
  24. <dependency>
  25. <groupId>org.apache.commons</groupId>
  26. <artifactId>commons-text</artifactId>
  27. <version>1.8</version>
  28. </dependency>

生成token的工具类:

  1. package com.lzl.utils;
  2. import io.jsonwebtoken.*;
  3. import org.apache.commons.lang3.time.DateUtils;
  4. import javax.crypto.spec.SecretKeySpec;
  5. import java.security.Key;
  6. import java.util.Date;
  7. //jwt工具类
  8. public class JwtHelper {
  9. // 生成Jwt
  10. public static String jwsWithHS(SignatureAlgorithm signatureAlgorithm, String userInfo, int num, String secret) {
  11. Key key = new SecretKeySpec(secret.getBytes(), signatureAlgorithm.getJcaName());
  12. Claims claims = Jwts.claims();
  13. claims.setSubject(userInfo); // jwt的信息的本身
  14. claims.setExpiration(DateUtils.addSeconds(new Date(), num)); //设置jwt多少秒过期
  15. String jws = Jwts.builder()
  16. .setClaims(claims).signWith(key, signatureAlgorithm).compact();
  17. return jws;
  18. }
  19. // 校验Jwt
  20. public static Jwt verifySign(String jws, String secret, SignatureAlgorithm signatureAlgorithm) {
  21. Key key = new SecretKeySpec(secret.getBytes(), signatureAlgorithm.getJcaName());
  22. Jwt jwt = Jwts.parserBuilder().setSigningKey(key).build().parse(jws);
  23. return jwt;
  24. }
  25. }

本次使用的secret:
在这里插入图片描述
由于我们需要在controller中使用,所以把两个参数,密钥和加密方式配置在yml文件中

这个secret可以去https://jwt.io/网站生成
在这里插入图片描述

上边选择加密方法,下边输入你想加密的信息,左侧会显示出加密后的信息。

2.编写LoginController和UserController

  1. package com.lzl.controller;
  2. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  3. import com.lzl.pojo.User;
  4. import com.lzl.service.UserService;
  5. import com.lzl.utils.JwtHelper;
  6. import io.jsonwebtoken.SignatureAlgorithm;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.beans.factory.annotation.Value;
  9. import org.springframework.web.bind.annotation.RequestMapping;
  10. import org.springframework.web.bind.annotation.RestController;
  11. import java.util.HashMap;
  12. import java.util.Map;
  13. /**
  14. * --效率,是成功的核心关键--
  15. *
  16. * @Author lzl
  17. * @Date 2023/3/21 08:21
  18. */
  19. @RestController
  20. @RequestMapping("/login")
  21. public class LoginController {
  22. @Value("${jwt.secret}")
  23. private String secret;
  24. @Value("${jwt.signature-algorithm}")
  25. private String algorithm;
  26. @Autowired
  27. private UserService service;
  28. @RequestMapping("/verify")
  29. public Map<String,Object> loginVerify(User user){
  30. Map<String, Object> map = new HashMap<>();
  31. //去库中查询
  32. QueryWrapper<User> wrapper = new QueryWrapper<>();
  33. wrapper.eq("username",user.getUsername())
  34. .eq("password",user.getPassword());
  35. User one = service.getOne(wrapper);
  36. if(one != null){
  37. map.put("username",one.getUsername());
  38. //登录成功,生成token
  39. String token = JwtHelper.jwsWithHS(
  40. SignatureAlgorithm.forName(algorithm),
  41. user.getUsername(),
  42. 3600,
  43. secret
  44. );
  45. map.put("token",token);
  46. }
  47. return map;
  48. }
  49. }
  50. package com.lzl.controller;
  51. import com.lzl.pojo.User;
  52. import com.lzl.service.UserService;
  53. import org.springframework.beans.factory.annotation.Autowired;
  54. import org.springframework.web.bind.annotation.RequestMapping;
  55. import org.springframework.web.bind.annotation.RestController;
  56. import java.util.List;
  57. /**
  58. * <p>
  59. * 前端控制器
  60. * </p>
  61. *
  62. * @author zhenLong
  63. * @since 2023-03-20
  64. */
  65. @RestController
  66. @RequestMapping("/user")
  67. public class UserController {
  68. @Autowired
  69. private UserService service;
  70. @RequestMapping("/getAll")
  71. public List<User> getUser(){
  72. return service.list();
  73. }
  74. }

3.编写跨域拦截器和token验证拦截器

  1. package com.lzl.interceptor;
  2. import org.springframework.web.servlet.HandlerInterceptor;
  3. import org.springframework.web.servlet.ModelAndView;
  4. import javax.servlet.http.HttpServletRequest;
  5. import javax.servlet.http.HttpServletResponse;
  6. /**
  7. * --效率,是成功的核心关键--
  8. * 拦截器,用于处理跨域请求
  9. *
  10. * @Author lzl
  11. * @Date 2022/10/1 10:20
  12. */
  13. public class CrossOriginInterceptor implements HandlerInterceptor {
  14. //主要逻辑:在handler之前执行:抽取handler中的冗余代码
  15. @Override
  16. public boolean preHandle(HttpServletRequest request,
  17. HttpServletResponse response, Object handler) throws Exception {
  18. String origin = request.getHeader("Origin");
  19. // 允许的跨域
  20. response.setHeader("Access-Control-Allow-Origin",origin);
  21. // 允许携带Cookie
  22. response.setHeader("Access-Control-Allow-Credentials","true");
  23. // 允许的请求头 预检请求需要这个设置
  24. response.setHeader("Access-Control-Allow-Headers", "Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization,Access-Token,token");
  25. response.setHeader("Access-Control-Expose-Headers", "*");//响应客户端的头部 允许携带Token 等等
  26. response.setHeader("Access-Control-Max-Age", "3600"); // 预检请求的结果缓存时间
  27. if (request.getMethod().equals("OPTIONS")){
  28. return false;
  29. }
  30. return true;
  31. }
  32. //在handler之后执行:进一步的响应定制
  33. @Override
  34. public void postHandle(HttpServletRequest request,
  35. HttpServletResponse response, Object handler,
  36. ModelAndView modelAndView) throws Exception {
  37. }
  38. //在页面渲染完毕之后,执行:资源回收
  39. @Override
  40. public void afterCompletion(HttpServletRequest request,
  41. HttpServletResponse response, Object handler, Exception ex)
  42. throws Exception {
  43. }
  44. }
  45. package com.lzl.interceptor;
  46. import com.fasterxml.jackson.databind.ObjectMapper;
  47. import org.springframework.web.servlet.HandlerInterceptor;
  48. import javax.servlet.http.HttpServletRequest;
  49. import javax.servlet.http.HttpServletResponse;
  50. import java.util.HashMap;
  51. import java.util.Map;
  52. /**
  53. * --效率,是成功的核心关键--
  54. * token验证拦截器
  55. * @Author lzl
  56. * @Date 2023/3/21 08:19
  57. */
  58. public class VerifyTokenInterceptor implements HandlerInterceptor {
  59. //在controller之前执行
  60. @Override
  61. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  62. String token = request.getHeader("token");
  63. /*
  64. 请求走到这,证明是非login方法(login方法已经被放行),验证token是否存在,不存在直接拦截
  65. 存在?验证是否有效,有效放行,无效拦截
  66. */
  67. if (token == null){
  68. response.setStatus(500);
  69. Map<String, Object> map = new HashMap<>();
  70. response.setContentType("application/json;charset=utf-8");
  71. //is null or token unavailable
  72. map.put("msg","登陆状态已过期");
  73. map.put("code",500);
  74. response.getWriter().write(new ObjectMapper().writeValueAsString(map));
  75. return false;
  76. }
  77. return true;
  78. }
  79. }

4.全局拦截器配置

在springboot中配置拦截器需要对拦截器进行注册

  1. package com.lzl.config;
  2. import com.lzl.interceptor.CrossOriginInterceptor;
  3. import com.lzl.interceptor.VerifyTokenInterceptor;
  4. import org.springframework.context.annotation.Configuration;
  5. import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
  6. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
  7. /**
  8. * --效率,是成功的核心关键--
  9. * 拦截器配置
  10. *
  11. * @Author lzl
  12. * @Date 2023/1/26 14:12
  13. */
  14. @Configuration
  15. public class GlobalInterceptorConfig implements WebMvcConfigurer {
  16. @Override //拦截器配置
  17. public void addInterceptors(InterceptorRegistry registry) {
  18. registry.addInterceptor(new CrossOriginInterceptor()) //拦截器跨域注册对象
  19. .addPathPatterns("/**"); //指定要拦截的请求
  20. registry.addInterceptor(new VerifyTokenInterceptor())//token验证拦截器
  21. .addPathPatterns("/**")
  22. .excludePathPatterns("/login/*"); //排除请求
  23. }
  24. }

三、业务逻辑

登陆状态验证的业务逻辑很简单,主要流程如下:
首先,用户没有登陆的情况下,用户无法访问除了login路径下的接口,以外的任何一个接口,访问的时候直接将页面重定向到登录页,若是前后端分离项目,则给前端返回错误状态码,让前端将用户跳转到登陆页。用户通过login接口输入正确的帐号和密码,后端生成一个token,并返回给前端,可以设置在请求头,或者以参数的形式传递,当前端拿到这个token时就可以访问登陆以外的页面了。

四、测试

当没有登陆状态时

在这里插入图片描述

访问login方法登陆,传回来一个token

在这里插入图片描述

携带token访问后端方法

在这里插入图片描述
访问成功


总结

这里只是对token简单的使用,在微服务架构中,token一般用于单点登陆验证,即登陆完成后,将token传到redis中存储,当访问除了登陆以外的其它服务时,去redis中查找。

发表评论

表情:
评论列表 (有 0 条评论,146人围观)

还没有评论,来说两句吧...

相关阅读