无状态token和前端拦截器介绍

一:什么是有状态

       有状态服务,即服务端需要记录每次会话的客户端信息,从而识别客户端身份,根据用户身份进行请求的处理,典型的设计如tomcat中的session。传统项目中,在用户登录后我们把登录者的信息保存在服务端session中,并且给用户一个cookie值,记录对应的session。然后下次请求,用户携带cookie值来,我们就能识别到对应session,从而找到用户的信息。

当登录数量过大,就需要我们新增服务器了。但是利用tomcat提供的session共享存在着不小的问题,缺点是什么?

  • 每台服务端都保存大量数据,增加服务端压力

  • 服务端保存用户状态,无法进行水平扩展

  • 客户端请求依赖服务端,多次请求必须访问同一台服务器

二:无状态token

  由于前后端分离项目无法使用Session作为前后端数据传递,所以无状态token就是为了取代Session的作用而出现【非前后端分离项目也可以使用无状态token】无状态token就是完全抛弃Session,使用Redis来替代,相当于自己实现Session方案,前后端传递token需要手动写代码传递,不能像Session一样自动传递,我们在做登录的时候,后端接收到前端传过来的数据在数据库中进行对比,对比成功后通过UUID生成一个随机的token值,将这个token作为key,将查询出来的对象作为value存入Redis,并设置过期时间为30分钟,然后将这个token和对象通过map或者对象的形式返回给前端,前端拿到对象进行数据回显,拿到token通过axios的请求拦截器加在请求头中,每次发送请求都携带token,后端通过这个token到Redis中取值,判断当前状态,从而实现了Session一样的功能,这就是无状态token

三:axios请求拦截器【前置拦截器】

  请求拦截器在每次发送axios请求之前执行,它会拦截一切axios请求,可以将token通过请求拦截器加在请求头中再发送请求到后端

//===============================axios请求拦截器【发请求前将token加到请求头】===============================
axios.interceptors.request.use(res=>{
  let token = localStorage.getItem("token");
  if(token){
    res.headers["token"] = token;
  }
  return res;
},error => {
  Promise.reject(error)
})

四:axios响应拦截器【后置拦截器】

  响应拦截器在每次后端响应后执行,它主要负责对后端响应的数据作出对应的处理,比如:未登录状态下请求后端接口被后端mvc拦截器拦截后响应给前端json数据,前端的响应拦截器获取到后跳转页面至登录界面

//===============================axios响应拦截器===============================
axios.interceptors.response.use(res => {
  //没登陆或者过期
  if (false === res.data.success || "noLogin" === res.data.msg) {
    localStorage.removeItem("token");
    localStorage.removeItem("logininfo");
    router.push({path: '/login'});
  }
  return res;
},error => {
  Promise.reject(error)
})

五:路由拦截器

  路由拦截器在每次发送前端请求后执行,一般作为权限校验,比如未登录的状态下访问前端其他界面,路由拦截器就会进行拦截,作为访问受限资源使用

//===============================路由拦截器【静态资源拦截器】===============================
router.beforeEach((to, from, next) => {
  //跳转登录注册页面清空localStorage
  if (to.path == '/login' || to.path == "/register") {
    localStorage.removeItem("token");
    localStorage.removeItem("logininfo");
    next();//放行
  }else{
    //判断是否登录
    let logininfo = localStorage.getItem('logininfo');
    if (logininfo) {
      next();
    } else {
      next({path: '/login'});//跳转到login
    }
  }
})

六:Springboot后端SpringMVC拦截器

  SpringMVC拦截器与SSM框架使用的拦截器是一样的,区别在于由于Springboot框架抛弃了xml的配置方式,需要使用类的形式进行配置拦截内容

  拦截器类【负责拦截逻辑处理】:

package cn.ybl.basic.interceptor;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.TimeUnit;

/**
 * @Author Mr.Yang
 * @createTime 2022/7/30 14:49
 * @Describe 登录拦截器
 */
@Component
public class LoginInterceptor implements HandlerInterceptor {

    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //获取请求头中的数据
        String token = request.getHeader("token");
        if(token!=null){
            //根据请求头key在Redis中取值
            Object logininfo = redisTemplate.opsForValue().get(token);
            if(logininfo!=null){    //说明没过期
                //重新设置过期时间
                redisTemplate.opsForValue().set(token,logininfo,30, TimeUnit.MINUTES);
                //放行
                return true;
            }
        }
        //请求头中没有数据或者登录信息过期了==============进行拦截
        //手动拼接一个json数据返回给前端
        response.setContentType("application/json;charset=utf8");
        response.getWriter().write("{\"success\":false,\"msg\":\"nologin\"}");
        return false;
    }
}

  拦截器配置类【负责配置拦截内容】:

package cn.ybl.basic.interceptor.config;

import cn.ybl.basic.interceptor.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @Author Mr.Yang
 * @createTime 2022/7/30 15:38
 * @Describe 拦截器配置类【配置拦截的请求】
 */
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                //拦截所有请求
                .addPathPatterns("/**")
                //放行登录请求
                .excludePathPatterns("/logins/**")
                //放行注册请求
                .excludePathPatterns("/register/**")
                //放行fastdfs请求
                .excludePathPatterns("/fastDfs/**")
                //放行图形验证码请求
                .excludePathPatterns("/verifyCode/**")
                //放行店铺入驻
                .excludePathPatterns("/shop/settlement")
                //放行swagger
                .excludePathPatterns("//swagger-resources/**\", \"/webjars/**\", \n" +
                        "                                 \"/v2/**\", \"/swagger-ui.html/**");
    }
}

八:静态资源拦截器

用在没有使用路由的界面,拦截不经过后端的请求,限制访问前端受限资源

//====前端拦截器:拦截不经过后端代码的请求【前端的有些页面是需要登录之后才能访问的】====//
//获取当前请求的url
let url = location.href;
//访问受限资源————如果不是登录页面并且也不是注册页面【为了测试:假如index.html是需要登录之后才能访问的】
if(url.indexOf("login.html")==-1 && url.indexOf("register.html")==-1){
    let logininfo = localStorage.getItem("logininfo");
    if(!logininfo){//如果访问的不是登录页面和注册页面,并且loginInfo没有值【没有登录或过期】
        location.href = "../login.html";
    }
}

 

posted @ 2022-07-30 17:27  yyybl  阅读(825)  评论(0)    收藏  举报