中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

深入淺析 Spring Security 緩存請求問題

發布時間:2020-08-19 22:08:26 來源:腳本之家 閱讀:373 作者:mrr 欄目:編程語言

為什么要緩存?

為了更好的描述問題,我們拿使用表單認證的網站舉例,簡化后的認證過程分為7步:

  1. 用戶訪問網站,打開了一個鏈接(origin url)。
  2. 請求發送給服務器,服務器判斷用戶請求了受保護的資源。
  3. 由于用戶沒有登錄,服務器重定向到登錄頁面
  4. 填寫表單,點擊登錄
  5. 瀏覽器將用戶名密碼以表單形式發送給服務器
  6. 服務器驗證用戶名密碼。成功,進入到下一步。否則要求用戶重新認證(第三步)
  7. 服務器對用戶擁有的權限(角色)判定: 有權限,重定向到origin url; 權限不足,返回狀態碼403("forbidden").

從第3步,我們可以知道,用戶的請求被中斷了。

用戶登錄成功后(第7步),會被重定向到origin url,spring security通過使用緩存的request,使得被中斷的請求能夠繼續執行。

使用緩存

用戶登錄成功后,頁面重定向到origin url。瀏覽器發出的請求優先被攔截器RequestCacheAwareFilter攔截,RequestCacheAwareFilter通過其持有的RequestCache對象實現request的恢復。

public void doFilter(ServletRequest request, ServletResponse response,
      FilterChain chain) throws IOException, ServletException {

    // request匹配,則取出,該操作同時會將緩存的request從session中刪除
    HttpServletRequest wrappedSavedRequest = requestCache.getMatchingRequest(
        (HttpServletRequest) request, (HttpServletResponse) response);

    // 優先使用緩存的request
    chain.doFilter(wrappedSavedRequest == null ? request : wrappedSavedRequest,
        response);
  }

何時緩存

首先,我們需要了解下RequestCache以及ExceptionTranslationFilter。

RequestCache

RequestCache接口聲明了緩存與恢復操作。默認實現類是HttpSessionRequestCache。HttpSessionRequestCache的實現比較簡單,這里只列出接口的聲明:

public interface RequestCache {
  // 將request緩存到session中
  void saveRequest(HttpServletRequest request, HttpServletResponse response);
  // 從session中取request
  SavedRequest getRequest(HttpServletRequest request, HttpServletResponse response);
  // 獲得與當前request匹配的緩存,并將匹配的request從session中刪除
  HttpServletRequest getMatchingRequest(HttpServletRequest request,
      HttpServletResponse response);
  // 刪除緩存的request
  void removeRequest(HttpServletRequest request, HttpServletResponse response);
}

ExceptionTranslationFilter

ExceptionTranslationFilter 是Spring Security的核心filter之一,用來處理AuthenticationException和AccessDeniedException兩種異常。

在我們的例子中,AuthenticationException指的是未登錄狀態下訪問受保護資源,AccessDeniedException指的是登陸了但是由于權限不足(比如普通用戶訪問管理員界面)。

ExceptionTranslationFilter 持有兩個處理類,分別是AuthenticationEntryPoint和AccessDeniedHandler。

ExceptionTranslationFilter 對異常的處理是通過這兩個處理類實現的,處理規則很簡單:

  1. 規則1. 如果異常是 AuthenticationException,使用 AuthenticationEntryPoint 處理
  2. 規則2. 如果異常是 AccessDeniedException 且用戶是匿名用戶,使用 AuthenticationEntryPoint 處理
  3. 規則3. 如果異常是 AccessDeniedException 且用戶不是匿名用戶,如果否則交給 AccessDeniedHandler 處理。

對應以下代碼

private void handleSpringSecurityException(HttpServletRequest request,
      HttpServletResponse response, FilterChain chain, RuntimeException exception)
      throws IOException, ServletException {
    if (exception instanceof AuthenticationException) {
      logger.debug(
          "Authentication exception occurred; redirecting to authentication entry point",
          exception);
      sendStartAuthentication(request, response, chain,
          (AuthenticationException) exception);
    }
    else if (exception instanceof AccessDeniedException) {
      if (authenticationTrustResolver.isAnonymous(SecurityContextHolder
          .getContext().getAuthentication())) {
        logger.debug(
            "Access is denied (user is anonymous); redirecting to authentication entry point",
            exception);
        sendStartAuthentication(
            request,
            response,
            chain,
            new InsufficientAuthenticationException(
                "Full authentication is required to access this resource"));
      }
      else {
        logger.debug(
            "Access is denied (user is not anonymous); delegating to AccessDeniedHandler",
            exception);
        accessDeniedHandler.handle(request, response,
            (AccessDeniedException) exception);
      }
    }
  }

AccessDeniedHandler 默認實現是 AccessDeniedHandlerImpl。該類對異常的處理是返回403錯誤碼。

public void handle(HttpServletRequest request, HttpServletResponse response,
      AccessDeniedException accessDeniedException) throws IOException,
      ServletException {
  if (!response.isCommitted()) {
    if (errorPage != null) { // 定義了errorPage
      // errorPage中可以操作該異常
      request.setAttribute(WebAttributes.ACCESS_DENIED_403,
          accessDeniedException);
      // 設置403狀態碼
      response.setStatus(HttpServletResponse.SC_FORBIDDEN);
      // 轉發到errorPage
      RequestDispatcher dispatcher = request.getRequestDispatcher(errorPage);
      dispatcher.forward(request, response);
    }
    else { // 沒有定義errorPage,則返回403狀態碼(Forbidden),以及錯誤信息
      response.sendError(HttpServletResponse.SC_FORBIDDEN,
          accessDeniedException.getMessage());
    }
  }
}

AuthenticationEntryPoint 默認實現是 LoginUrlAuthenticationEntryPoint, 該類的處理是轉發或重定向到登錄頁面

public void commence(HttpServletRequest request, HttpServletResponse response,
      AuthenticationException authException) throws IOException, ServletException {
  String redirectUrl = null;
  if (useForward) {
    if (forceHttps && "http".equals(request.getScheme())) {
      // First redirect the current request to HTTPS.
      // When that request is received, the forward to the login page will be
      // used.
      redirectUrl = buildHttpsRedirectUrlForRequest(request);
    }
    if (redirectUrl == null) {
      String loginForm = determineUrlToUseForThisRequest(request, response,
          authException);
      if (logger.isDebugEnabled()) {
        logger.debug("Server side forward to: " + loginForm);
      }
      RequestDispatcher dispatcher = request.getRequestDispatcher(loginForm);
      // 轉發
      dispatcher.forward(request, response);
      return;
    }
  }
  else {
    // redirect to login page. Use https if forceHttps true
    redirectUrl = buildRedirectUrlToLoginPage(request, response, authException);
  }
  // 重定向
  redirectStrategy.sendRedirect(request, response, redirectUrl);
}

了解完這些,回到我們的例子。

第3步時,用戶未登錄的情況下訪問受保護資源,ExceptionTranslationFilter會捕獲到AuthenticationException異常(規則1)。頁面需要跳轉,ExceptionTranslationFilter在跳轉前使用requestCache緩存request。

protected void sendStartAuthentication(HttpServletRequest request,
      HttpServletResponse response, FilterChain chain,
      AuthenticationException reason) throws ServletException, IOException {
  // SEC-112: Clear the SecurityContextHolder's Authentication, as the
  // existing Authentication is no longer considered valid
  SecurityContextHolder.getContext().setAuthentication(null);
  // 緩存 request
  requestCache.saveRequest(request, response);
  logger.debug("Calling Authentication entry point.");
  authenticationEntryPoint.commence(request, response, reason);
}

一些坑

在開發過程中,如果不理解Spring Security如何緩存request,可能會踩一些坑。

舉個簡單例子,如果網站認證是信息存放在header中。第一次請求受保護資源時,請求頭中不包含認證信息 ,驗證失敗,該請求會被緩存,之后即使用戶填寫了信息,也會因為request被恢復導致信息丟失從而認證失敗(問題描述可以參見這里。

最簡單的方案當然是不緩存request。

spring security 提供了NullRequestCache, 該類實現了 RequestCache 接口,但是沒有任何操作。

public class NullRequestCache implements RequestCache {
  public SavedRequest getRequest(HttpServletRequest request,
      HttpServletResponse response) {
    return null;
  }
  public void removeRequest(HttpServletRequest request, HttpServletResponse response) {
  }
  public void saveRequest(HttpServletRequest request, HttpServletResponse response) {
  }
  public HttpServletRequest getMatchingRequest(HttpServletRequest request,
      HttpServletResponse response) {
    return null;
  }
}

配置requestCache,使用如下代碼即可:

http.requestCache().requestCache(new NullRequestCache());

補充

默認情況下,三種request不會被緩存。

  1. 請求地址以/favicon.ico結尾
  2. header中的content-type值為application/json
  3. header中的X-Requested-With值為XMLHttpRequest

可以參見:RequestCacheConfigurer類中的私有方法createDefaultSavedRequestMatcher。

附上實例代碼: https://coding.net/u/tanhe123/p/SpringSecurityRequestCache

以上所述是小編給大家介紹的Spring Security 緩存請求問題,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對億速云網站的支持!
如果你覺得本文對你有幫助,歡迎轉載,煩請注明出處,謝謝!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

铁力市| 尤溪县| 柳林县| 潜江市| 广汉市| 马龙县| 竹溪县| 汤原县| 广安市| 江安县| 瑞金市| 乌鲁木齐市| 金平| 安丘市| 商丘市| 三江| 长丰县| 那曲县| 高尔夫| 高密市| 习水县| 盐池县| 武乡县| 镇江市| 临沂市| 昌邑市| 阳朔县| 福贡县| 渝中区| 吉木萨尔县| 修文县| 广汉市| 如皋市| 左云县| 芒康县| 崇仁县| 西乌| 余江县| 陵水| 全椒县| 贞丰县|