单点登录

2020/11/27 posted in  工作问题

Redis+Cookie+Jackson+Filter实现单点登录

单点登录实现

封装Cookie

    //COOKIE_NAME cookie 的名字
    //COOKIE_DOMAIN 要设置的域
    //写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);
        ck.setMaxAge(60 * 60 * 24 * 365);//如果是-1 代表永久,若果不设置则不写入硬盘,只写入内存,在当前页面有效
        log.info("write cookieName:{},cookieValue:{}", ck.getName(), ck.getValue());
        response.addCookie(ck);
    }
    //读cookie
    public static String readLoginCookie(HttpServletRequest request) {
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (Cookie ck : cookies) {
                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;
    }
    //删除cookie
    public static void delLoginToken(HttpServletRequest request, HttpServletResponse response) {
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (Cookie ck : cookies) {
                log.info("read cookieName{},cookievalue{}", ck.getName(), ck.getValue());
                if (StringUtils.equals(ck.getName(), COOKIE_NAME)) {
                    ck.setDomain(COOKIE_DOMAIN);
                    ck.setPath("/");
                    ck.setMaxAge(0);
                    log.info("del cookieName{},cookievalue{}", ck.getName(), ck.getValue());
                    response.addCookie(ck);
                    return;
                }
            }
        }
    }
domain 和cookie
X:domain=".xx.com" cookie
a:A.xx.com cookie:domain=A.xx.com;path="/"
b:B.xx.com cookie:domain=B.xx.com;path="/"
c:A.xx.com/test/cc cookie:domain=A.xx.com;path="/test/cc"
d:A.xx.com/test/dd cookie:domain=A.xx.com;path="/test/dd"
e:A.xx.com/test cookie:domain=A.xx.com;path="/test"

注意:a、b不能互相拿到对方的cookie,c、d 可以共享a、e的cookie

Redis包装

    //exTime seconds
    public static String setEx(String key, String value, int exTime) {
        Jedis jedis = null;
        String result = null;
        try {
            jedis = RedisPool.getJedis();
            result = jedis.setex(key, exTime, value);
        } catch (Exception e) {
            log.error("setex key:{} value {} error", key, value, e);
            RedisPool.returnBrokenResource(jedis);
            return result;
        }
        RedisPool.returnBrokenResource(jedis);
        return result;
    }

以登录为例实现

    @RequestMapping(value = "login.do", method = RequestMethod.POST)
    @ResponseBody
    public ServerResponse<User> login(String username, String password, HttpSession session, HttpServletResponse httpServletResponse) {
        ServerResponse<User> response = iUserService.login(username, password);
        if (response.isSuccess()) {
            CookieUtil.writeLoginToken(httpServletResponse,session.getId());
            RedisPoolUtil.setEx(session.getId(), JsonUtil.obj2str(response.getData()), Const.RedisCacheExtime.REDIS_SESSION_EXTIME);
        }
        return response;
    }
流程:
  1. 获取session种到cookie中同时存user的json串到redis中
  2. 对于需要验证登录的操作,首先获取cookie然后拿到token,以此为键获取redis中的数据

session有效期的问题

登录后所有使用用户登录验证的操作都应重新设置session有效期,但是上述功能不能实现session的重置

SessionExpireFilter 重置session有效期

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String loginToken = CookieUtil.readLoginCookie(request);
        if (StringUtils.isNotEmpty(loginToken)){
            String userJsonStr = RedisPoolUtil.get(loginToken);
            User user = JsonUtil.str2obj(userJsonStr,User.class);
            if (user != null) {
                RedisPoolUtil.expire(loginToken, Const.RedisCacheExtime.REDIS_SESSION_EXTIME);
            }
        }
        filterChain.doFilter(servletRequest,servletResponse);
    }

如果有user则重新设置redis的过期时间

总结

  1. 使用cookie的damain在一级域名下种下cookie
  2. 使用redis的ex特性存储user信息,键为cookie的value值,值为user对象的json值
  3. 通过filter来重置session的有效期
  4. 通过设置cookie的setMaxAge实现cookie存储在本地磁盘