日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

為什么要分布式 Session 呢?

請參考下圖:

Spring Session 原理分析

 

當后臺集群部署時,單機的 Session 維護就會出現問題。

假設登錄的認證授權發生在 Tomcat A 服務器上, Tomcat A 在本地存儲了用戶 Session ,并簽發認證令牌,用于驗證用戶身份。

下次請求可能分發給 Tomcat B 服務器,而 Tomcat B 并沒有用戶 Session ,用戶攜帶的認證令牌無效,得到 401 。

Spring Session 原理分析

 

除了 JWT 無狀態的認證方式,另一種主流的實現方案就是采用分布式 Session 。

public interface HttpSession {
    public void setAttribute(String name, Object value);
}

HttpSession 內的存儲就是 name 與 value 的鍵值對映射,且存在過期時間,這與 redis 的設計相符合,分布式 Session 通常使用 Redis 進行實現。

無論是在單機環境,還是在引入了 Spring Session 的集群環境下,代碼實現都是相同的,即屏蔽了底層的細節,可以在不改動 HttpSession 使用的相關代碼的情況下,實現 Session 存儲環境的切換。

logger.debug("記錄當前用戶ID");
httpSession.setAttribute(UserService.USER_ID, persistUser.getId());

這聽起來很酷,那么 Spring Session 具體是如何在不改動代碼的情況下進行 Session 存儲環境切換的呢?

原理

官方文檔: How HttpSession Integration Works - Spring Session

回顧

之前在學習 Spring Security 原理之時,我們從官方文檔中找到了這樣一張圖。

Spring Session 原理分析

 

所有的認證授權攔截都是基于 Filter 實現的,而這里的 Spring Session ,也是基于 Filter 。

原理分析

因為 HttpSession 和 HttpServletRequest (獲取 HttpSession 的 API )都是接口,這意味著可以將這些 API 替換成自定義的實現。

核心源碼如下:

注:以下代碼中部分無關代碼已被刪減。

public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFilter {

  @Override
  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) {
    /** 替換 request */
    SessionRepositoryRequestWrApper wrappedRequest = new SessionRepositoryRequestWrapper(request, response, this.servletContext);
    /** 替換 response */
    SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(wrappedRequest, response);
    /** try-finally,finally 必定執行 */
    try {
      /** 執行后續過濾器鏈 */
      filterChain.doFilter(wrappedRequest, wrappedResponse);
    } finally {
      /** 后續過濾器鏈執行完畢,提交 session,用于存儲 session 信息并返回 set-cookie 信息 */
      wrappedRequest.commitSession();
    }
  }
}

response 封裝器核心源碼如下:

private final class SessionRepositoryResponseWrapper extends OnCommittedResponseWrapper {

  SessionRepositoryResponseWrapper(SessionRepositoryRequestWrapper request, HttpServletResponse response) {
    super(response);
    this.request = request;
  }

  @Override
  protected void onResponseCommitted() {
    /** response 提交后提交 session */
    this.request.commitSession();
  }
}

request 封裝器核心源碼如下:

private final class SessionRepositoryRequestWrapper extends HttpServletRequestWrapper {

  private SessionRepositoryRequestWrapper(HttpServletRequest request, HttpServletResponse response, ServletContext servletContext) {
    super(request);
    this.response = response;
    this.servletContext = servletContext;
  }

  /**
   * 將 sessionId 寫入 reponse,并持久化 session
   */
  private void commitSession() {
    /** 獲取當前 session 信息 */
    S session = getCurrentSession().getSession();
    /** 持久化 session */
    SessionRepositoryFilter.this.sessionRepository.save(session);
    /** reponse 寫入 sessionId */
    SessionRepositoryFilter.this.httpSessionIdResolver.setSessionId(this, this.response, session.getId());
  }

  /**
   * 重寫 HttpServletRequest 的 getSession 方法
   */
  @Override
  public HttpSessionWrapper getSession(boolean create) {
    /** 從持久化中查詢 session */
    S requestedSession = getRequestedSession();
    /** session 存在,直接返回 */
    if (requestedSession != null) {
      currentSession = new HttpSessionWrapper(requestedSession, getServletContext());
      currentSession.setNew(false);
      return currentSession;
    }
    /** 設置不創建,返回空 */
    if (!create) {
      return null;
    }
    /** 創建 session 并返回 */
    S session = SessionRepositoryFilter.this.sessionRepository.createSession();
    currentSession = new HttpSessionWrapper(session, getServletContext());
    return currentSession;
  }

  /**
   * 從 repository 查詢 session
   */
  private S getRequestedSession() {
    /** 查詢 sessionId 信息 */
    List<String> sessionIds = SessionRepositoryFilter.this.httpSessionIdResolver.resolveSessionIds(this);
    /** 遍歷查詢 */
    for (String sessionId : sessionIds) {
      S session = SessionRepositoryFilter.this.sessionRepository.findById(sessionId);
      if (session != null) {
        this.requestedSession = session;
        break;
      }
    }
    /** 返回持久化 session */
    return this.requestedSession;
  }

  /**
   * http session 包裝器
   */
  private final class HttpSessionWrapper extends HttpSessionAdapter<S> {

    HttpSessionWrapper(S session, ServletContext servletContext) {
      super(session, servletContext);
    }

    @Override
    public void invalidate() {
      super.invalidate();
      /** session 不合法,從存儲中刪除信息 */
      SessionRepositoryFilter.this.sessionRepository.deleteById(getId());
    }
  }
}

原理簡單,裝飾 HttpSession , Session 失效時從存儲中刪除,在請求結束之后,存儲 session 。

總結

分布式環境下的認證方案: JWT 與分布式 Session 。

個人覺得兩種方案都很好, JWT ,無狀態,服務器不用維護 Session 信息,但如何讓 JWT 失效是一個難題。

分布式 Session ,使用起來簡單,但需要額外的存儲空間。

實際應用中,要兼顧當前的業務場景與安全性進行方案的選擇。

分享到:
標簽:Spring
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定