Spring Security認證成功後回跳(解決前後端分離下OAuth2認證成功回跳)
前言
Spring Security(後面簡稱SS)用了很長時間了,但之前一直沒注意到一個有趣的特性,直到最近弄前後端分離,在OAuth2提供者(github)認證後,需要跳回前端頁面(前端頁面和服務端不在同個域下),然後突然一般情況下(同域),SS認證後會自動跳回認證前使用者想訪問的資源。由此開始尋找這個magic。
問題:SS是怎麼在認證成功後自動跳轉到認證前使用者想訪問的資源(url)?
原本我以為SS是跟SS OAuth的實現一樣,通過在http報文裡面傳遞這個url,但是看瀏覽器的報文內容,在認證過程中是沒有傳遞過url的。而且在OAuth裡面,OAuth2 provider這種第三方服務,不可能幫你傳遞這個引數,所以比較好的辦法就是利用session,這也解釋前後端分離情況下,SS不會(不能)幫我們跳回去前端頁面
問題的切口:SavedRequestAwareAuthenticationSuccessHandler
之前我用SS經常接觸到這個類,但是我只知道它作為認證成功的handler,在認證成功後會進行一個跳轉操作。這裡是部分原始碼
public class SavedRequestAwareAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { protected final Log logger = LogFactory.getLog(this.getClass()); private RequestCache requestCache = new HttpSessionRequestCache(); @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException { //這裡取出了一個request SavedRequest savedRequest = requestCache.getRequest(request, response); if (savedRequest == null) { super.onAuthenticationSuccess(request, response, authentication); return; } String targetUrlParameter = getTargetUrlParameter(); if (isAlwaysUseDefaultTargetUrl() || (targetUrlParameter != null && StringUtils.hasText(request .getParameter(targetUrlParameter)))) { requestCache.removeRequest(request, response); super.onAuthenticationSuccess(request, response, authentication); return; } clearAuthenticationAttributes(request); // Use the DefaultSavedRequest URL !!關鍵的一句!! String targetUrl = savedRequest.getRedirectUrl(); logger.debug("Redirecting to DefaultSavedRequest Url: " + targetUrl); // 這裡有一個很明顯的跳轉操作,追蹤targetUrl怎麼來的 getRedirectStrategy().sendRedirect(request, response, targetUrl); } public void setRequestCache(RequestCache requestCache) { this.requestCache = requestCache; } }
這裡有一個很奇怪的屬性——savedRequest
,從上面就可以看到,它是來自requestCache
,requestCache
的型別是HttpSessionRequestCache。這裡可以看到,第一行程式碼就從requestCache
的getRequest
方法中取出了savedRequest
,看一下這個方法
public SavedRequest getRequest(HttpServletRequest currentRequest, HttpServletResponse response) { HttpSession session = currentRequest.getSession(false); if (session != null) { return (SavedRequest) session.getAttribute(SAVED_REQUEST); } return null; }
這裡就印證了我們前面的猜測,的確是儲存在session裡面,那麼SS什麼時候放進去的?畢竟我想要前後端分離下OAuth2認證後跳回前端頁面
很容易猜測是呼叫HttpSessionRequestCache的setRequest
方法放進去,可以搜尋,這裡我是用上面的sso demo的日誌找到ExceptionTranslationFilter的sendStartAuthentication
方法(把日誌級別調到debug)
ExceptionTranslationFilter
ExceptionTranslationFilter
原始碼裡面註釋的第一句話就說明了它的用處
Handles any AccessDeniedException and AuthenticationException
thrown within the filter chain.
這裡也解釋了各種認證filter諸如UsernamePasswordAuthenticationFilter
為什麼可以隨便丟擲AuthenticationException
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);
requestCache.saveRequest(request, response);
logger.debug("Calling Authentication entry point.");
authenticationEntryPoint.commence(request, response, reason);
}
到這裡大概明白了SS怎麼儲存和跳轉回認證前使用者想訪問的資源,總結一下。SS通過ExceptionTranslationFilter
在認證開始前把request快取到session中,當認證成功後,在SavedRequestAwareAuthenticationSuccessHandler
裡取出快取的request,跳轉回認證前使用者想訪問的url
解決前後端分離下OAuth2認證後跳回前端頁面
理解了SS怎麼處理認證成功自動跳轉問題,解決前後端分離下OAuth2認證成功跳轉就很容易了。這裡先說一下前後端跨域下OAuth2認證的流程(OAuth2協議具體請自己谷歌,我也說不清楚)
oauth2.png
這個流程比同域OAuth2情況下少了一步,同域下第一步應該是訪問一個受保護資源,然後才開始上面流程,所以我暫時的做法是
在/login介面處接受一個auth_url引數,表示認證成功後跳轉到這個url,然後認證成功後取出這個url進行跳轉
這樣只需要修改兩處地方
繼承OAuth2ClientAuthenticationProcessingFilter
class MyOAuth2ClientAuthenticationProcessingFilter extends OAuth2ClientAuthenticationProcessingFilter{
private RequestCache requestCache = new HttpSessionRequestCache();
public MyOAuth2ClientAuthenticationProcessingFilter(String defaultFilterProcessesUrl) {
super(defaultFilterProcessesUrl);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
requestCache.saveRequest(request, response);
return super.attemptAuthentication(request, response);
}
}
重寫這個filter的AuthenticationSuccessHandler
filter.setAuthenticationSuccessHandler((request, response, authentication) -> {
String authUrl = request.getParameter("auth_url");
response.sendRedirect(authUrl);
});
這樣就簡單粗暴地實現了OAuth2下認證成功後可以自動跳轉回認證前想訪問的資源了。
作者:zerouwar
連結:https://www.jianshu.com/p/39f46c8de9c1
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。