加载中

前后端Token验证

实例环境

IDE框架/脚手架
IDEA v2019.1.2Spring boot 2+
-Vue cli 3+

概述

  1. 前端携带账户信息请求登录 -> 后端生成带有寿命的token -> 前端存储tokencookie/localStorage
  2. 前端每次请求api都携带token头字段 -> 后端检测token并续期
  3. 前端退出 -> 后端清除token

前端

前端除登录外每次请求api都需要携带token头字段,为了方便操作,可以给数据请求框架( Ajax、fetch、axios等)加一个全局过滤器(或拦截器)

例如axios的语法为:

axios.interceptors.request.use(config => {
  // 这里使用的是 vue 的状态管理,store.state.user 表示 user 状态
  // user.token 在登录成功后进行赋值,同时存进cookie防止浏览器关闭造成的登录状态丢失
  // 登录状态:进行过滤
  // 未登录状态:不进行过滤
  if (store.state.user.token) {
    config.headers.Authorization = `${store.state.user.token}`;
  }
  return config;
},err => {
  return Promise.reject(err);
});

添加了"Authorization"头字段后效果如下:
添加token字段示意图

后端

后端生成的token需带有生命期,防止前端过长时间不去退出而造成账户安全问题,针对生命期的管理,这里使用Redis存储系统,实现token存储删除超时自动删除功能

Redis安装使用

下载地址

// 启动,启动后后台运行
// 默认服务端口6379
redis-server.exe redis.windows.conf

// 操作,启动上面那个至后才能操作
redis-cli.exe -h 127.0.0.1 -p 6379

Token管理代码

要使用redisTemplate就必须开启Redis服务

生成token

@Autowired
private RedisTemplate<Object, Object> redisTemplate;

/**
 * 生成token
 * @param userId 用户ID
 * @return Token
 * */
public String createToken(int userId){
    UUID uuid = UUID.randomUUID();  // 根据机器和时间生成唯一字符
    String token = userId + "_" + uuid.toString().replace("-","");  // token = userId_uuid(去-)
    String key = userId + "_token";
    redisTemplate.opsForValue().set(key, token,
            12, TimeUnit.HOURS);  // redis set操作:key,value,时间 12,单位小时
    return token;
}

检测Token

/**
 * 检查token
 * @param token
 * @return true更新token;false令牌不存在
 * */
public Boolean checkToken(String token){
    if(token == null || token.equals("")){  // 空token 返回false
        return false;
    }
    String[] arr1 = token.split("_");   // 分解token
    if(arr1.length != 2){   // 格式不对返回false
        return false;
    }
    try {
        String key = arr1[0] + "_token";
        String r_token = redisTemplate.opsForValue().get(key).toString();   // 读取服务器token
        if(r_token == null || ! r_token.equals(token)){ // 服务器token 过期 或 与用户token 不相等返回false
            return false;
        }
        redisTemplate.opsForValue().set(key, token, // 更新token时长
                12, TimeUnit.HOURS);
        return true;
    }catch (Exception e){
        System.out.println(e);
    }
    return false;
}

移除Token

/**
 * 注销token
 * @param token
 * @return true成功;false失败
 * */
public Boolean clearToken(String token){
   if(token == null || token.equals("")){  // 空token 返回false
       return false;
   }
   String[] arr1 = token.split("_");   // 分解token
   if(arr1.length != 2){   // 格式不对返回false
       return false;
   }
   try {
       String key = arr1[0] + "_token";
       String r_token = redisTemplate.opsForValue().get(key).toString();   // 读取服务器token
       if(r_token == null || ! r_token.equals(token)){ // 服务器token 过期 或 与用户token 不相等返回false
           return false;
       }
       redisTemplate.delete(key);
       return true;
   }catch (Exception e){
       System.out.println(e);
   }
   return false;
}

拦截器配置

前端发送的除登录以外的每一次请求,都应进行拦截,检测Token

注意:前端在进行复杂跨域请求前,会进行一次预请求(OPTIONS),试探性的服务器响应是否正确,然后才会发送真正请求,所以拦截器也应放行预请求

@Configuration
public class TokenInterceptor extends WebMvcConfigurationSupport {

    @Autowired
    private TokenManager tokenManager;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    
        HandlerInterceptor handlerInterceptor=new HandlerInterceptor() {

            public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object hander) throws Exception {
            
                // 放行OPTIONS请求,防止因跨域导致的请求失败
                if (request.getMethod().toUpperCase().equals("OPTIONS")) {
                    return true;
                }
                
                // 非OPTIONS请求TOKEN验证
                String token = request.getHeader("authorization");
                if (token != null) {
                    boolean flag = tokenManager.checkToken(token);
                    if (flag) {
                        return true;
                    }
                }
                
                return false;
                
            }

        };

		// 拦截路径配置,不拦截 login(exclude表示排除)
        registry.addInterceptor(handlerInterceptor).excludePathPatterns("/*/login");

    }
}
posted @ 2020-02-03 12:15  jialeYang  阅读(307)  评论(0编辑  收藏  举报