• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

springboot 整合 JWT 和请求拦截,实现 token 做请求安全拦截校验,且实现阻止并发登录

武飞扬头像
Johnson_9
帮助1

目录

一、导入依赖

二、编写 jwt 工具类,实现生成 token 和解析 token

三、在登录请求中向redis中添加token信息

1、先注入redis的接口类

2、在登录方法中生成token并插入redis,有效期一天

四、实现请求拦截器

1、编写自定义的请求拦截器

2、实现WebMvcConfigurer接口,重写实现其添加拦截器方法

五、测试总结

1、请求拦截

①正确 token

②错误的token

③空token

④从redis中删掉token

2、阻止并发登录

3、总结


一、导入依赖

导入 jwt 的依赖

  1.  
    <!-- jjwt-->
  2.  
    <dependency>
  3.  
    <groupId>io.jsonwebtoken</groupId>
  4.  
    <artifactId>jjwt</artifactId>
  5.  
    <version>0.9.1</version>
  6.  
    </dependency>

二、编写 jwt 工具类,实现生成 token 和解析 token

jwt 工作流程

学新通

 可以传入具体的用户信息,方便解析校验

  1.  
    // Jwt工具类
  2.  
    public class JwtUtil {
  3.  
     
  4.  
    //private static long time = 1000*10; // token 有效期为10秒
  5.  
    private static long time = 1000*60*60*24; // token 有效期为一天
  6.  
    private static String signature = "admin";
  7.  
     
  8.  
    // 生成token ,三个参数是我实体类的字段,可根据自身需求来传,一般只需要用户id即可
  9.  
    public static String createJwtToken(String operNo,String operName ,String organNo){
  10.  
    JwtBuilder builder = Jwts.builder();
  11.  
    String jwtToken = builder
  12.  
    // header
  13.  
    .setHeaderParam("typ","JWT")
  14.  
    .setHeaderParam("alg","HS256")
  15.  
    // payload 载荷
  16.  
    .claim("operNo",operNo)
  17.  
    .claim("operName",operName)
  18.  
    .claim("organNo",organNo)
  19.  
    .claim("date",new Date())
  20.  
    .setSubject(operNo)
  21.  
    .setExpiration(new Date(System.currentTimeMillis() time))
  22.  
    .setId(UUID.randomUUID().toString())
  23.  
    // signature 签名信息
  24.  
    .signWith(SignatureAlgorithm.HS256,signature)
  25.  
    // 用.拼接
  26.  
    .compact();
  27.  
    return jwtToken;
  28.  
    }
  29.  
     
  30.  
    // 验证token是否还有效,返回具体内容
  31.  
    public static Claims checkToken(String token){
  32.  
    if(token == null){
  33.  
    return null;
  34.  
    }
  35.  
    JwtParser parser = Jwts.parser();
  36.  
    try {
  37.  
    Jws<Claims> claimsJws = parser.setSigningKey(signature).parseClaimsJws(token);
  38.  
    Claims claims = claimsJws.getBody();
  39.  
    System.out.println(claims.get("operNo"));
  40.  
    System.out.println(claims.get("operName"));
  41.  
    System.out.println(claims.get("organNo"));
  42.  
    System.out.println(claims.getId());
  43.  
    System.out.println(claims.getSubject()); // 签名
  44.  
    System.out.println(claims.getExpiration()); // 有效期
  45.  
    // 如果解析token正常,返回claims
  46.  
    return claims;
  47.  
    }catch (Exception e) {
  48.  
    // 如果解析token抛出异常,返回null
  49.  
    return null;
  50.  
    }
  51.  
     
  52.  
    }
  53.  
     
  54.  
    }
学新通

三、在登录请求中向redis中添加token信息

1、先注入redis的接口类

如果不知道怎么配置redis的可以去看这篇文章 => springboot整合redis并实现mybatis二级缓存

  1.  
    @Autowired
  2.  
    StringRedisTemplate redisTemplate;

2、在登录方法中生成token并插入redis,有效期一天

redis中key值使用字符串 "operToken" 加上用户 id 拼接而成,value 就是 token 的具体内容

也可以插入一个map,redis的键依旧为字符串 "operToken" 加上用户 id 拼接而成,map中的键为token版本号(可以更好的验证并发登录替换了token,不同的随机数即可),值为token的具体内容

  1.  
    // 插入JWT的token
  2.  
    String token = JwtUtil.createJwtToken(loginOper.getOperNo(),loginOper.getOperName(),loginOper.getOrganNo());
  3.  
    loginOper.setToken(token);
  4.  
    // 将JWT的token存入redis,有效期一天
  5.  
    redisTemplate.opsForValue().set("operToken" loginOper.getOperNo(),token,1, TimeUnit.DAYS);

四、实现请求拦截器

三个请求拦截器

  1.  
    /**前置处理:在业务处理器处理请求之前被调用*/
  2.  
    boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
  3.  
    throws Exception;
  4.  
     
  5.  
    /**中置处理:在业务处理器处理请求执行完成后,生成视图之前执行。后处理(调用了Service并返回ModelAndView,但未进行页面渲染),有机会修改ModelAndView ,现在这个很少使用了*/
  6.  
    void postHandle(
  7.  
    HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
  8.  
    throws Exception;
  9.  
     
  10.  
    /**后置处理:在DispatcherServlet完全处理完请求后被调用,可用于清理资源等*/
  11.  
    void afterCompletion(
  12.  
    HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
  13.  
    throws Exception;

在请求处理之前,切面的给每个请求做一个校验,校验请求头中的token信息是否有效且信息正确

1、编写自定义的请求拦截器

先取出请求头中token的信息,然后判断这个token是否存在,如果存在再去校验这个token是否真实有效,如果有效再取出token中的用户信息,根据用户id来找出redis中的token,再判断redis中的token是否存在,不存在则说明过期了,如果存在则继续对比请求头中的token是否一致,不一致的话则说明token错误或者被别人并发登录,这里就没有办法判断具体是哪种情况,所以用map集合来存入redis就可以更大程度的判断出(先判断最新版本号的token是否和请求头中的相同,再判断过往版本号的token是否有相同,如果有则说明并发登录,如果没有则token错误。加入map之前需要判断map的大小是否超过一定数值,比如5,超过5则删除之前的数据;对map里键值对单独设置过期时也可以)当然,这还是不够精准

  1.  
    @Component // @Component注解一定要加上
  2.  
    public class Interceptor implements HandlerInterceptor {
  3.  
    // 注入redis
  4.  
    @Autowired
  5.  
    StringRedisTemplate redisTemplate;
  6.  
     
  7.  
    // 处理请求之前执行
  8.  
    @Override
  9.  
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  10.  
    // 取出请求头中Authorization的信息,就是token内容,接下来就是各种判断
  11.  
    String requestToken = request.getHeader("Authorization");
  12.  
    if(!StringUtils.isEmpty(requestToken)){
  13.  
    Claims claims = JwtUtil.checkToken(request.getHeader("Authorization"));
  14.  
    if (claims != null) {
  15.  
    String token = redisTemplate.opsForValue().get("operToken" claims.get("operNo"));
  16.  
    if(Boolean.TRUE.equals(redisTemplate.hasKey("operToken" claims.get("operNo")))){
  17.  
    if(requestToken.equals(token)){
  18.  
    // token正确
  19.  
    return true;
  20.  
    }else {
  21.  
    // token错误,判为并发登录,挤下线
  22.  
    // 对应的修改响应头的状态,用于前端判断做出相应的策略
  23.  
    response.setStatus(411);
  24.  
    return false;
  25.  
    }
  26.  
    }else {
  27.  
    // token不存在于redis中,已过期
  28.  
    response.setStatus(410);
  29.  
    return false;
  30.  
    }
  31.  
    }
  32.  
    // 解析token中的用户信息claims为null
  33.  
    response.setStatus(409);
  34.  
    return false;
  35.  
    }
  36.  
    // requestToken为空
  37.  
    response.setStatus(409);
  38.  
    return false;
  39.  
    }
  40.  
     
  41.  
    // 处理请求之后执行
  42.  
    @Override
  43.  
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
  44.  
    System.out.println("处理请求之后执行");
  45.  
    }
  46.  
     
  47.  
    }
学新通

2、实现WebMvcConfigurer接口,重写实现其添加拦截器方法

  1.  
    @Component
  2.  
    public class InterceptorConfig implements WebMvcConfigurer {
  3.  
    // 注入自定义拦截器
  4.  
    @Autowired
  5.  
    private Interceptor interceptor;
  6.  
     
  7.  
    // 重写添加拦截器方法
  8.  
    @Override
  9.  
    public void addInterceptors(InterceptorRegistry registry){ // InterceptorRegistry 为拦截器注册对象
  10.  
    registry.addInterceptor(interceptor) // 注册自定义拦截器
  11.  
    .addPathPatterns("/sys/basic-api/**")// 拦截的路径
  12.  
    .excludePathPatterns(); // 不拦截的路径
  13.  
    }
  14.  
    }

五、测试总结

1、请求拦截

给刚刚的自定义拦截器加上输出

学新通

然后进行测试

①正确 token

学新通

 可以发现是先打印输出结果,再执行请求,创建SqlSession

②错误的token

学新通

因为是错误的token,token校验是通过不了的,因此返回的用户信息也为空,打印完直接结束,没有执行请求

③空token

学新通

 打印完直接结束,没有执行请求

④从redis中删掉token

学新通

 redis中查不到token,判定为token过期

2、阻止并发登录

因为每次生成的token中都有随机id,所以每次登录时生成的token肯定都不一样

所以并发登陆以后,之前登录的用户再一次发送请求就会被验证拦截,根据返回的411状态码来判断账号已在别处登录

学新通

学新通

 学新通

3、总结

利用token来实现请求拦截校验是完全没有问题的,但是现实中可以尽可能的多加几层校验,确保足够的安全

token的存储方式还是有很大的改进空间,虽然也能勉强实现阻止多并发登录,但是不够完善,不能精准判断出具体的错误情况,所以由此引出 spring security

传送门  ==> 前后端分离项目整合spring security,自定义登录验证接口,并精准有效阻止并发登录

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgbhaka
系列文章
更多 icon
同类精品
更多 icon
继续加载