使用Redis實現叢集單點登入
阿新 • • 發佈:2018-12-25
由於單點環境下,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); //單位是秒。然後,將當前sessionId寫入到redis中//如果這個maxage不設定的話,cookie就不會寫入硬碟,而是寫在記憶體。只在當前頁面有效。 ck.setMaxAge(60 * 60 * 24 * 365);//如果是-1,代表永久 log.info("write cookieName:{},cookieValue:{}",ck.getName(),ck.getValue()); response.addCookie(ck); }
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