前后端分离之用户登录状态管理和校验

最新更新时间:2019年06月10日17:23:05
《猛戳-查看我的博客地图-总有你意想不到的惊喜》

本文内容:前后端开发分离,如何实现用户登录状态监控和校验,要解决的几个问题:

  • A用户登录A机器成功,此时A用户登录B机器,A机器的A用户登录状态需要下线,即保证一个用户只能同时在线一台机器;
  • A用户登录成功,一周之内不需要再次登录;
实现原理
  • 用户首次访问站点,后端从请求体中读取cookie,此时cookie中的token为空,用户需要进行登录;
  • 用户首次登录成功,后端的response向浏览器写入cookie,在cookie中存储该用户唯一的并且有时效性的token;
  • 用户二次访问站点,如果token没有失效(过期或更新,过期:是指我们设定的一周的有效期;更新:是指用户在其他设备/浏览器登录后,redis数据库会更新token),不做处理;如果失效,重新设定路由指向登录页,引导用户重新登录;
  • 如果用户退出操作,调用退出接口,后端的response会主动删除浏览器中的cookie;
概述

常规的PC站点开发,如果有访问权限控制,需要用户登录才能访问,因此合理安全的方案至关重要,本文方案的具体实现过程如下:

  • 前端请求后端的每一个接口,需要对接口返回值的code码进行分类处理,我们定义,在任意页面的任意接口返回值code码为11115时,表示用户登录身份失效,此时需要重新登录,用路由控制直接跳转到登录页面;
  • 后端在接收任意一个请求时,需要在Spring MVC拦截器Interceptor中做处理,对用户登录状态进行判断,读取浏览器中的cookie和redis数据库进行对比,如果cookie中的token有效,返回正常结果,如果token失效,返回11115 code码;
前端代码
 fetch().then((Response)=>{
      let data = Response.json();//res是一个json文件,使用 json() 读取并解析数据,返回一个被解析为JSON格式的promise对象
      data.then((res,rej)=>{
        if(res.code == 100){
        
        }else if(res.code == 11115){
          //用户登录失效,token失效,跳转到登录页面
          history.replace('/login')
          window.location.reload()
        }else{
        
        }
      })
    }).catch((e)=>{
    
    })
后端代码

每个接口的拦截,使用Interceptor拦截器

//HandlerInterceptor接口
package org.springframework.web.servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public interface HandlerInterceptor {
    boolean preHandle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception;
    void postHandle(HttpServletRequest var1, HttpServletResponse var2, Object var3, ModelAndView var4) throws Exception;
    void afterCompletion(HttpServletRequest var1, HttpServletResponse var2, Object var3, Exception var4) throws Exception;
}

//HandlerInterceptor接口的实现
public class AuthInterceptor implements HandlerInterceptor {
	//引入 声明
    @Autowired
    private RedisManage redisManage;

	//重写
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
    	String Origin = request.getHeader("Origin");
    	if(Origin != null){
	    	response.setHeader("Access-Control-Allow-Origin",Origin);
	        response.setHeader("Access-Control-Allow-Credentials", "true");
	        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
	        response.setHeader("Access-Control-Max-Age", "3600");
	        response.setHeader("Access-Control-Allow-Headers", "DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type, Accept-Language, Origin, Accept-Encoding");
    	}
        String url = request.getRequestURI();
    	boolean isLogout = false;
        String token = "";
        Cookie[] cookies = request.getCookies();
        if(cookies == null) {
            isLogout = true;
        }else {
        	token = cookies.getName();//没有这个方法cookies.getName(),此处举个例子
        }
        if(token == ‘’) {
            isLogout = true;
        }else {
        	//查询redis数据库
            if(redisManage.getValue(token) == null){
                isLogout = true;
            }else{
                String tokenInRedis = (String) redisManage.getValue(token);
                if(tokenInRedis != token) {
                    isLogout = true;
                }
            }
        }
        if(isLogout) {
            //清空session
            request.getSession().invalidate();
            //清空cookies信息
            Cookie tokenInRedis = new Cookie("token","");
            tokenInRedis.setMaxAge(0);
            tokenInRedis.setHttpOnly(true);
            //响应中写入cookie
            response.addCookie(tokenInRedis);
            ServiceResult serviceResult = new ServiceResult(60*60*24*7);
            response.getWriter().write(JSONObject.toJSONString(serviceResult));
            response.getWriter().flush();
            return false;
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {

    }
}

登录 类

public ServiceResult login(@RequestBody User user, HttpServletRequest request , HttpServletResponse response){
	ServiceResult serviceResult = userService.login(user);
	String userName = 'wanshaobo';
	if(serviceResult.getCode() == 100){
		String md5Token = MD5Util.md5(userName + U2AESKey + "REMIXED" + Math.random());
		Cookie cookie01 = new Cookie("token",md5Token);
		//7天过期时间,单位为秒
		U2TokenCookie.setMaxAge(60*60*24*7);
		U2TokenCookie.setHttpOnly(true);
		U2TokenCookie.setPath("/");
		response.addCookie(cookie01);
		//存储token到redis
		redisManage.putValueExpireTimes(userName,md5Token,60*60*24*7L);
	}
	return serviceResult;
}

登出 类

public ServiceResult logout(HttpServletRequest request,HttpServletResponse response) {
	String token = "";
	//清空cookies信息
	Cookie[] cookies = request.getCookies();
	for (Cookie coo : cookies) {
		if ("token" == coo.getName()) {
			coo.setMaxAge(0);
			response.addCookie(coo);
		}
	}
	//清空redis中的token
	redisManage.delValue(token);
	return new ServiceResult(HttpResultEnum.SUCCESS);
}
参考资料

感谢阅读,欢迎评论^-^

打赏我吧^-^

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值