前言:最近的項目中用到了spring security組件,說句顯low的話:我剛開始都不知道用了security好不勒,提了bug,在改的過程中,遇到了一些問題,找同事交流,才知道是用的security組件。 這個bug,真的是一波三折:復現它就是個問題,然后我又把403改成了404,后來干脆登錄不進去主站,最后,這個bug,被消滅在本寶寶的代碼中,哈哈哈哈哈!
問題所在:token 過期
一、關於問題的想法
1,我在想是不是寫的登錄邏輯有問題,用代碼控制住了當前登錄頁的相關緩存問題,一路跟代碼。 好吧,有些工程沒權限就不說了,后來同事告訴我應該沒又跳轉到邏輯,就死掉了。 我在登錄頁發現了那個 security的標記(當時根本不知道那是個啥,傻X一個),查了查,再跟同事了解了具體情況,果斷放棄對邏輯的懷疑,因為我們沒有用代碼控制緩存失效之類的
2,既然不是用戶代碼的問題,那就是組件機制的問題唄。 本寶寶立馬翻了spring security的文檔,里面真的提到了超時的概念。 並提出了集中解決方案,鏈接地址如下:https://docs.spring.io/spring-security/site/docs/4.1.3.RELEASE/reference/htmlsingle/#csrf-timeouts 好吧,里面提到了通過使用JS函數,在表單提交前獲取到一個token值(通過使用spring提供的CsrfTokenArgumentResolver) 然而本寶寶並沒有在這時候干掉這個不能算是bug的被提出的bug.也提到自定義處理相關異常的建議。 不過本寶寶一向都不是安分守己的人,好不容易整出一個bug,不折騰會兒,會遭天譴的,以下就是本寶寶驗證演算的方案示例:
a:本來就是spring security的一種安全保護機制,不算bug,去跟測試和產品協商,忽略它——哈哈,當然這是下下策,雖然最為省事兒,但我預估99%會死
b:既然是停留時間過長才產生的問題,那我想辦法,讓用戶不管在什么時候點擊登錄,就跟他剛剛輸入完賬戶信息一樣。 所以我就想到了:<METAHTTP-EQUIV="REFRESH"CONTENT="csrf_timeout_in_seconds"> 然而,在我的案例里面,並沒有用啊,憂傷。
c:使用token注冊機,弄出一個不過時的,給登錄頁面使用——哈哈,我想的挺好的。 再不行了,我找到關於這個token過期時間的代碼,改掉它——果然一副寫代碼到死的精神
d:惹急了我,我把這個csrf的token驗證功能關掉——簡單又粗暴吧,嘿嘿——當然,這是決定不能干的事兒,雖然可以解決我那個bug
e:總之是出了異常,系統才會攔截到,然后跳轉到了指定的403頁面,那我就在它跳轉到403頁面之前,截住這個異常,然后由我自定義處理
f:這一條其實和第 d 條類似,我既然不能關掉所有的驗證,那我關掉登錄頁的驗證總行吧,登錄頁校驗的本來就不是這個頁面,而是用戶的權限。誰都可以進入到登錄頁,不是嗎,嘿嘿。 然后,本寶寶改寫了攔截器,對登錄頁的請求放行了。 ——然而,可能是人品有問題,我竟然沒法兒正常登錄了,憂傷。肯定是哪兒被我寫錯了。
其實我想法可多了,總有一個能干掉這個bug,在幾次憂傷之后,我選了自定義異常攔截這種方案,這條線的思路是:斷點,看看它在跳轉到403之前,到底攔截到了什么異常;找到異常,我自定義攔截,並做出我想要系統做出的反應
二、關鍵代碼
自定義異常處理:
import org.springframework.security.access.AccessDeniedException; import org.springframework.security.web.access.AccessDeniedHandlerImpl; import org.springframework.security.web.csrf.MissingCsrfTokenException; import org.springframework.security.web.savedrequest.HttpSessionRequestCache; import org.springframework.security.web.savedrequest.RequestCache; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; /** * @author 何紅霞~Angelina */ public class MissingCsrfTokenAccessDeniedHandler extends AccessDeniedHandlerImpl { private RequestCache requestCache = new HttpSessionRequestCache(); private String loginPage = "/login"; @Override public void handle(HttpServletRequest req, HttpServletResponse res, AccessDeniedException exception) throws IOException, ServletException { if (exception instanceof MissingCsrfTokenException && isSessionInvalid(req)) { requestCache.saveRequest(req, res); req.getRequestDispatcher(loginPage).forward(req, res); } super.handle(req, res, exception); } private boolean isSessionInvalid(HttpServletRequest req) { try { HttpSession session = req.getSession(false); return session == null || !req.isRequestedSessionIdValid(); } catch (IllegalStateException ex) { return true; } } }
好吧,其實這里又鬧了一個笑話。 我最開始,把轉發搞成重定向了,我靠,之前是token丟失,現在是整個參數都沒了,想掐死自己的心都有了。 果真是久了不寫基礎代碼,手生啊!
安全配置:
@Bean public AccessDeniedHandler getAccessDeniedHandler() { return new MissingCsrfTokenAccessDeniedHandler(); }
http.exceptionHandling().accessDeniedHandler(getAccessDeniedHandler());
三、總結
按照上面的方法做,問題解決了。 反正目前我自測是沒毛病了,有問題再說吧,哈哈哈哈哈。
其實這之中發生了幾個插曲,我覺得可以分享一下:
1,最開始是跳轉到403頁面,結果我一通改,403是不跳了,但跳404了。 這時候,你怎么想問題? 反正我想的是:我既然能把403改成跳到404,我就一定能讓它跳轉到我想要讓它去的地方,我離成功只差最后一點了。 有時候,最恐怖的不是bug被改變了,而是不管你怎么改,那個bug都沒有變過,你知道這意味着什么嗎?
2,我有時候覺得,我是心里變態的,因為我特別希望系統整出個大bug,最好是那種,無從下手的bug。 這想法不好,我得改!
3,我覺得,改bug是一件很有成就感和幸福感的事兒,我以前是苦逼的挖坑,現在是幸福的挖坑,然后幸福的填坑。 我改bug可開心了,果然天生的勞碌命,一輩子的碼農啊。。。。。。