分布式会话拦截器

思路

用户执行登录,注册等操作时,后端会生成一个 token
放入 cookie
中返回给前端,同时保存到 redis
中, 前端后续涉及验证 token
http
请求都需要在 header
中携带 token
和用户 userId
, 后端设置 interceptor
拦截指定请求,拿到 header
中的 token
, 后端通过 userId
redis
中查找,如果查到并且二者匹配的上,次请求通过拦截,否则 http
请求无法到达 controller

分布式会话拦截器
image-20200629113855412

拦截器 vs 过滤器

过滤器和拦截器有相似之处,都能对 Servlet 请求二次加工。但是过滤器并不是 SpringBoot 规范中的概念,事实上,过滤器是 Servlet 规范中的事物。

因此过滤器和拦截器的最大区别就是他们存在的空间是不一样的。

Filter 拦截器是 Servlet 中的规范,它可不依赖于 Spring,它是由 Servlet 容器 Filter 每个请求和响应。它可以在请求到达 Servlet 之前就处,因此 Filter 也总是优先于 Interceptor 执行。

分布式会话拦截器
img

nterceptor 过滤器是工作在 Spring 容器中的,由 Spring 所控制,因此能和 Spring 紧密的结合,在 Spring 中使用拦截器,处理拦截行为更方便,事实上 Filter 能做的事情,Interceptor 也都能实现。

分布式会话拦截器
img

拦截器和过滤器执行时机

分布式会话拦截器
IjOIC

springboot 过滤器使用

https://www.baeldung.com/spring-boot-add-filter
@Component
@Order(1)
public class TransactionFilter implements Filter {
  private static final Logger LOGGER = LoggerFactory.getLogger(TransactionFilter.class);

  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    HttpServletRequest req = (HttpServletRequest) request;

    LOGGER.info( "Starting a transaction for req : {}",
            req.getRequestURI());

    chain.doFilter(request, response);
    LOGGER.info(
            "Committing a transaction for req : {}",
            req.getRequestURI());
  }
}
复制代码

springboot 拦截器使用

请求->过滤器->拦截器的preHandle()->控制器Controller->拦截器的postHandle()->视图页面渲染->拦截器的afterCompletion()

分布式会话拦截器
img

1 声明自定义拦截器

/**
 * 自定义:用户会话请求拦截器
 * 
 * @author shengding
 */
@Component
public class UserTokenInterceptor implements HandlerInterceptor {
  private static final Logger LOGGER = LoggerFactory.getLogger(UserTokenInterceptor.class);

  @Autowired
  RedisOperator redisOperator;

  /**
   * 拦截请求,请求访问到controller之前
   * @param request
   * @param response
   * @param handler
   * @return false : 请求被拦截,被驳回
   *         true  : 请求通过拦截器,是OK的,是可以放行的
   * @throws Exception
   */
  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

    // 对于复杂的Ajax 跨域请求,浏览器会首先发送一个 OPTIONS 请求,如果返回200,则继续发送真正的请求
    // 配置了拦截器后,由于 OPTIONS请求时不携带 自定义header的,所以 preHandle返回false, OPTIONS请求被拦截,
    // 后续跨域设置失效
    if(HttpMethod.OPTIONS.name().equals(request.getMethod().toUpperCase())) {
      return true;
    }

    // http请求header中携带的token
    String requestHeaderUserToken = request.getHeader("headerUserToken");

    // http请求header中携带的userId
    String requestHeaderUserId = request.getHeader("headerUserId");
    String redisUserToken = redisOperator.get(RedisKey.USERTOKEN.getKey() + ":" + requestHeaderUserId);

    LOGGER.info("请求进入拦截器--->, 请求header中userId: {}, token: {}; redis中用户token: {}", requestHeaderUserId, requestHeaderUserToken, redisUserToken);
    // http携带token不为空且与redis所存的token一致,则通过拦截,否则视为非法请求
    if(StringUtils.isNotBlank(requestHeaderUserId) && StringUtils.isNotBlank(requestHeaderUserToken)) {
      if(StringUtils.isBlank(redisUserToken)) {
        returnErrorResponse(response, JSONResult.errMsg("请登录..."));
        return false;
      } else {
        if(!requestHeaderUserToken.equals(redisUserToken)) {
          returnErrorResponse(response, JSONResult.errMsg("账号异地登录..."));
          return false;
        }
      }
    } else {
      returnErrorResponse(response, JSONResult.errMsg("请登录..."));
      return false;
    }
    return true;
  }

  /**
   * 请求访问到controller, 渲染视图之前
   * @param request
   * @param response
   * @param handler
   * @param modelAndView
   * @throws Exception
   */
  @Override
  public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

  }

  /**
   * 请求访问到controller, 渲染视图之后
   * @param request
   * @param response
   * @param handler
   * @param ex
   * @throws Exception
   */
  @Override
  public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

  }
}

复制代码

2 注册拦截器

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

  @Autowired
  UserTokenInterceptor userTokenInterceptor;

  /**
   * 注册拦截器(自定义拦截器只有注册了才生效)
   * @param registry
   */
  @Override
  public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(userTokenInterceptor)
            .addPathPatterns("/shopcart/add")
            .addPathPatterns("/shopcart/del")

            .addPathPatterns("/address/list")
            .addPathPatterns("/address/add")
            .addPathPatterns("/address/update")
            .addPathPatterns("/address/setDefault")
            .addPathPatterns("/address/del")

            .addPathPatterns("/orders/getPaidOrderInfo")
            .addPathPatterns("/orders/items")
            .addPathPatterns("/orders/receive")
            .addPathPatterns("/orders/create")

            .addPathPatterns("/comments/list")
            .addPathPatterns("/comments")

            .addPathPatterns("/center/trend")
            .addPathPatterns("/center/statusCounts")
            .addPathPatterns("/center/orders")
            .addPathPatterns("/center/userface")
            .addPathPatterns("/center/userInfo")

            .addPathPatterns("/pay/getPaidOrderInfo");
  }
}
复制代码

3 封装错误返回消息

  /**
   * 由于拦截器 preHandle 返回 boolean值, 当请求被拦截时若想返回json格式,需要手动设置response
   * @param response
   * @param jsonResult
   */
  public void returnErrorResponse(HttpServletResponse response, JSONResult jsonResult) {
    response.setContentType("application/json");
    response.setCharacterEncoding("utf-8");
    try(OutputStream outputStream = response.getOutputStream()) {
      outputStream.write(JsonUtils.objectToJson(jsonResult).getBytes());
      outputStream.flush();
    } catch(IOException e) {
      e.printStackTrace();
    }
  }
复制代码

4 前端发起请求

axios.post(
  serverUrl + '/orders/getPaidOrderInfo?orderId=' + orderId,
  {},
  {
    headers: {
      'headerUserId': userInfo.id,
      'headerUserToken': userInfo.userUniqueToken
    }
  })
复制代码

5 测试

分布式会话拦截器
image-20200629130436228

原文 

https://juejin.im/post/5ef975fae51d4534791d3cde

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » 分布式会话拦截器

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址