前言:最近的項目中用到了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可開心了,果然天生的勞碌命,一輩子的碼農啊。。。。。。