您好,登錄后才能下訂單哦!
為什么要緩存?
為了更好的描述問題,我們拿使用表單認證的網站舉例,簡化后的認證過程分為7步:
從第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 對異常的處理是通過這兩個處理類實現的,處理規則很簡單:
對應以下代碼
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不會被緩存。
可以參見:RequestCacheConfigurer類中的私有方法createDefaultSavedRequestMatcher。
附上實例代碼: https://coding.net/u/tanhe123/p/SpringSecurityRequestCache
以上所述是小編給大家介紹的Spring Security 緩存請求問題,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對億速云網站的支持!
如果你覺得本文對你有幫助,歡迎轉載,煩請注明出處,謝謝!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。