1. 程式人生 > >使用Redis實現叢集單點登入

使用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