使用Redis实现集群单点登录

       由于单点环境下,session直接存储在同一台服务下,用户登录直接获取session是没什么问题。但是在集群环境下,还是这种做法的话,由于session存储在不同服务上。假设有A和B两台服务器做成集群,它们负载均衡,如果登录请求是在A服务器下进行的,A服务下做保存session的操作,而后续的请求到B服务下,这时在B服务上获取不到对应的session(因为session是保存在A服务下),所以这时候就会提示用户未登录,这样对用户是不友好的。

    目前解决这种单点方式有很多,比如通过nginx的Ip hash,根据hash值分配用户只能请求某台服务器,但是这种做法是有缺陷的,因为根据这种计算结果,并不会均衡的分配请求。当然也可以使用spring session框架来零侵入的实现单点登录问题等。

    这里,我用的是jedis+filter+cookie+json 来原生实现单点登录。

   首先,用户登陆的时候,在cookie中写入相应的信息:

public static void writeLoginToken(HttpServletResponse response, String token){
    Cookie ck = new Cookie(COOKIE_NAME,token);
    ck.setDomain(COOKIE_DOMAIN);
    ck.setPath("/");//代表设置在根目录
    ck.setHttpOnly(true);
    //单位是秒。
    //如果这个maxage不设置的话,cookie就不会写入硬盘,而是写在内存。只在当前页面有效。
    ck.setMaxAge(60 * 60 * 24 * 365);//如果是-1,代表永久
    log.info("write cookieName:{},cookieValue:{}",ck.getName(),ck.getValue());
    response.addCookie(ck);
}
然后,将当前sessionId写入到redis中

RedisPoolUtil.setEx(session.getId(), JsonUtil.obj2String(response.getData()), Const.RedisCacheExtime.REDIS_SESSION_EXTIME);
这里redis的超时时间为1800秒即30分钟。

这样,当下一次,用户访问B服务器的时候,可以通过读取我们写入cookie中的值,来获取token的值。

public static String readLoginToken(HttpServletRequest request){
    Cookie[] cks = request.getCookies();
    if(cks != null){
        for(Cookie ck : cks){
            log.info("read cookieName:{},cookieValue:{}",ck.getName(),ck.getValue());
            if(StringUtils.equals(ck.getName(),COOKIE_NAME)){
                log.info("return cookieName:{},cookieValue:{}",ck.getName(),ck.getValue());
                return ck.getValue();
            }
        }
    }
    return null;
}
最后通过该token,去redis获取用户的信息。整体逻辑如下:

public ServerResponse addCategory(HttpServletRequest httpServletRequest, String categoryName, @RequestParam(value = "parentId", defaultValue = "0") int parentId) {
    String loginToken = CookieUtil.readLoginToken(httpServletRequest);
    if (StringUtils.isEmpty(loginToken)) {
        return ServerResponse.createByErroMessage("用户未登陆,无法获取到用户的个人信息");
    }

    String userStr = RedisPoolUtil.get(loginToken);
    User user = JsonUtil.string2Obj(userStr, User.class);
    if (user == null) {
        return ServerResponse.createByCodeErroMessage(ResponseCode.NEED_LOGIN.getCode(), "用户未登录");
    }
    ServerResponse checkResult = userService.checkAdmin(user);
    if (!checkResult.isSuccess()) {
        return ServerResponse.createByErroMessage("当前用户不是管理员,无权进行此操作");
    }
    return categoryService.addCategory(categoryName, parentId);
}
但是到里,我们只是设置了token的过期时间,但实际的过程中,用户一旦有活动,都需要重置token过期时间,所以需要些个拦截器来重置过期时间:

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
    String loginToken = CookieUtil.readLoginToken(httpServletRequest);
    if (StringUtils.isNotEmpty(loginToken)) {
        String userStr = RedisPoolUtil.get(loginToken);
        User user = JsonUtil.string2Obj(userStr, User.class);
        if (user != null) {
            RedisPoolUtil.expire(loginToken, 60 * 30);
        }
    }
    filterChain.doFilter(servletRequest, servletResponse);
}
以上就是使用redis来解决单点登陆的问题,token值也可以使用自定义的uuid,只要有写入cookie当中就可以。

具体实现可参看个人github: https://github.com/Mrfirewind/mmall_learning



猜你喜欢

转载自blog.csdn.net/qq_34871626/article/details/79186030