最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

深入淺析 Spring Security 緩存請(qǐng)求問(wèn)題

 更新時(shí)間:2019年04月23日 09:51:36   投稿:mrr  
這篇文章主要介紹了 Spring Security 緩存請(qǐng)求問(wèn)題,本文通過(guò)實(shí)例文字相結(jié)合的形式給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友參考下吧

為什么要緩存?

為了更好的描述問(wèn)題,我們拿使用表單認(rèn)證的網(wǎng)站舉例,簡(jiǎn)化后的認(rèn)證過(guò)程分為7步:

  1. 用戶(hù)訪問(wèn)網(wǎng)站,打開(kāi)了一個(gè)鏈接(origin url)。
  2. 請(qǐng)求發(fā)送給服務(wù)器,服務(wù)器判斷用戶(hù)請(qǐng)求了受保護(hù)的資源。
  3. 由于用戶(hù)沒(méi)有登錄,服務(wù)器重定向到登錄頁(yè)面
  4. 填寫(xiě)表單,點(diǎn)擊登錄
  5. 瀏覽器將用戶(hù)名密碼以表單形式發(fā)送給服務(wù)器
  6. 服務(wù)器驗(yàn)證用戶(hù)名密碼。成功,進(jìn)入到下一步。否則要求用戶(hù)重新認(rèn)證(第三步)
  7. 服務(wù)器對(duì)用戶(hù)擁有的權(quán)限(角色)判定: 有權(quán)限,重定向到origin url; 權(quán)限不足,返回狀態(tài)碼403("forbidden").

從第3步,我們可以知道,用戶(hù)的請(qǐng)求被中斷了。

用戶(hù)登錄成功后(第7步),會(huì)被重定向到origin url,spring security通過(guò)使用緩存的request,使得被中斷的請(qǐng)求能夠繼續(xù)執(zhí)行。

使用緩存

用戶(hù)登錄成功后,頁(yè)面重定向到origin url。瀏覽器發(fā)出的請(qǐng)求優(yōu)先被攔截器RequestCacheAwareFilter攔截,RequestCacheAwareFilter通過(guò)其持有的RequestCache對(duì)象實(shí)現(xiàn)request的恢復(fù)。

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

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

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

何時(shí)緩存

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

RequestCache

RequestCache接口聲明了緩存與恢復(fù)操作。默認(rèn)實(shí)現(xiàn)類(lèi)是HttpSessionRequestCache。HttpSessionRequestCache的實(shí)現(xiàn)比較簡(jiǎn)單,這里只列出接口的聲明:

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

ExceptionTranslationFilter

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

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

ExceptionTranslationFilter 持有兩個(gè)處理類(lèi),分別是AuthenticationEntryPoint和AccessDeniedHandler。

ExceptionTranslationFilter 對(duì)異常的處理是通過(guò)這兩個(gè)處理類(lèi)實(shí)現(xiàn)的,處理規(guī)則很簡(jiǎn)單:

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

對(duì)應(yīng)以下代碼

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 默認(rèn)實(shí)現(xiàn)是 AccessDeniedHandlerImpl。該類(lèi)對(duì)異常的處理是返回403錯(cuò)誤碼。

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);
      // 設(shè)置403狀態(tài)碼
      response.setStatus(HttpServletResponse.SC_FORBIDDEN);
      // 轉(zhuǎn)發(fā)到errorPage
      RequestDispatcher dispatcher = request.getRequestDispatcher(errorPage);
      dispatcher.forward(request, response);
    }
    else { // 沒(méi)有定義errorPage,則返回403狀態(tài)碼(Forbidden),以及錯(cuò)誤信息
      response.sendError(HttpServletResponse.SC_FORBIDDEN,
          accessDeniedException.getMessage());
    }
  }
}

AuthenticationEntryPoint 默認(rèn)實(shí)現(xiàn)是 LoginUrlAuthenticationEntryPoint, 該類(lèi)的處理是轉(zhuǎn)發(fā)或重定向到登錄頁(yè)面

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);
      // 轉(zhuǎn)發(fā)
      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步時(shí),用戶(hù)未登錄的情況下訪問(wèn)受保護(hù)資源,ExceptionTranslationFilter會(huì)捕獲到AuthenticationException異常(規(guī)則1)。頁(yè)面需要跳轉(zhuǎn),ExceptionTranslationFilter在跳轉(zhuǎn)前使用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);
}

一些坑

在開(kāi)發(fā)過(guò)程中,如果不理解Spring Security如何緩存request,可能會(huì)踩一些坑。

舉個(gè)簡(jiǎn)單例子,如果網(wǎng)站認(rèn)證是信息存放在header中。第一次請(qǐng)求受保護(hù)資源時(shí),請(qǐng)求頭中不包含認(rèn)證信息 ,驗(yàn)證失敗,該請(qǐng)求會(huì)被緩存,之后即使用戶(hù)填寫(xiě)了信息,也會(huì)因?yàn)閞equest被恢復(fù)導(dǎo)致信息丟失從而認(rèn)證失敗(問(wèn)題描述可以參見(jiàn)這里。

最簡(jiǎn)單的方案當(dāng)然是不緩存request。

spring security 提供了NullRequestCache, 該類(lèi)實(shí)現(xiàn)了 RequestCache 接口,但是沒(méi)有任何操作。

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());

補(bǔ)充

默認(rèn)情況下,三種request不會(huì)被緩存。

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

可以參見(jiàn):RequestCacheConfigurer類(lèi)中的私有方法createDefaultSavedRequestMatcher。

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

以上所述是小編給大家介紹的Spring Security 緩存請(qǐng)求問(wèn)題,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
如果你覺(jué)得本文對(duì)你有幫助,歡迎轉(zhuǎn)載,煩請(qǐng)注明出處,謝謝!

相關(guān)文章

最新評(píng)論

文山县| 汝阳县| 徐汇区| 红原县| 万山特区| 嫩江县| 海盐县| 岳西县| 墨竹工卡县| 收藏| 邳州市| 德州市| 芜湖县| 乌苏市| 闽清县| 民和| 兴海县| 嵩明县| 阳西县| 静海县| 高唐县| 姚安县| 乌苏市| 滕州市| 溆浦县| 景洪市| 临澧县| 股票| 江津市| 佛坪县| 中西区| 福泉市| 麻栗坡县| 合阳县| 托里县| 苗栗市| 静宁县| 永仁县| 津市市| 安泽县| 华亭县|