前言
結合上一期我們介紹的AuthenticationManager為入口的身份驗證的核心模塊挖滤,我們這次討論的是為了使SpringSecurity對Spring Web項目提供支持昙啄,作為驗證請求入口的窒所。
第三期 Authentication核心簡介
本期的任務清單
- AbstractAuthenticationProcessingFilter的依賴組件;
- AbstractAuthenticationProcessingFilter依賴組件的主要職責和相關設計動機。
1. AbstractAuthenticationProcessingFilter處理Request及與AuthenticationManager交互的流程
AbstractAuthenticationProcessingFilter的主要職責和依賴組件
了解AbstractAuthenticationProcessingFilter大概是干嘛的的最簡單的方法就是直接去讀api-doc。
Abstract processor of browser-based HTTP-based authentication requests.
官方文檔說的很明白了:處理基于瀏覽器交互的HTTP驗證請求。所以AbstractAuthenticationProcessingFilter的職責也就非常明確——處理所有HTTP Request和Response對象劝枣,并將其封裝成AuthenticationMananger可以處理的Authentication汤踏。并且在身份驗證成功或失敗之后將對應的行為轉換為HTTP的Response。同時還要處理一些Web特有的資源比如Session和Cookie舔腾∠海總結成一句話,就是替AuthenticationMananger把所有和Authentication沒關系的事情全部給包圓了稳诚。
繼續(xù)讀JavaDoc可以得知AbstractAuthenticationProcessingFilter為了完成組織上交代的與瀏覽器和HTTP請求的驗證任務哗脖。它將大任務拆成了幾個子任務并交給了以下組件完成:
- AuthenticationManager用于處理身份驗證的核心邏輯;
-
AuthenticationSuccessHandler
用于處理驗證成功的后續(xù)流程扳还; -
AuthenticationFailureHandler
用于處理失敗的后續(xù)流程才避; - 在驗證成功后發(fā)布一個名為
InteractiveAuthenticationSuccessEvent
的事件通知給到應用上下文,用于告知身份驗證已經(jīng)成功氨距; - 因為是基于瀏覽器所以相關的會話管理行為交由
SessionAuthenticationStrategy
來進行實現(xiàn)桑逝。 - 文檔上還有一點沒有寫出來的是,如果用戶開啟了類似“記住我”之類的免密碼登錄俏让,AbstractAuthenticationProcessingFilter還有一個名為RememberMeServices來進行管理楞遏。
AbstractAuthenticationProcessingFilter的驗證流程
AbstractAuthenticationProcessingFilter本質上還是個Filter,其核心的業(yè)務入口方法就是doFilter方法:
這里我們先設置一個問題首昔,然后帶著問題去分析AbstractAuthenticationProcessingFilter的doFilter都是怎么設計解決這些問題的寡喝?
- 怎么判斷當前的請求是需要被驗證訪問的?
- 如何進行身份驗證沙廉?
- 如何進行會話驗證?動機是什么臼节?
- 成功和失敗的后續(xù)流程都在干什么撬陵?
- Remember-Me功能實現(xiàn)流程是什么?
- 如何在其他服務中監(jiān)聽驗證成功的事件网缝?
問題1. 怎么判斷當前的請求是需要被驗證訪問的巨税?
在正式進行身份之前,doFilter會通過Security中的粉臊。嘗試查找是否有匹配記錄草添。
我們回顧下之前我們寫過的訪問控制的代碼:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
// inde.html對應的url允許所任人訪問
.antMatchers("/").permitAll()
// user.html對應的url,則需要用戶有USER的角色才可以訪問
.antMatchers("/user").hasRole("USER")
.and()
.formLogin();
}
其中的matcher的規(guī)則便會在這個流程中預先被檢查扼仲,如果需要進行身份驗證則會進行寫一個階段:對請求進行必要的身份驗證远寸。
問題2. 如何進行身份驗證?
doFilter中通過調用自己的attemptAuthentication方法屠凶,但并不進行身份驗證的邏輯處理驰后,而是委托AuthenticationManager去完成相關的身份驗證流程。AbstractAuthenticationProcessingFilter將HttpServletRequest包裝成了Authentication對象與核心的AuthenticationManager進行交互矗愧。這樣的設計可以使AuthenticationManager不感知外部的Web環(huán)境灶芝,從而使Security不僅可以支持Web應用,同時也可以被所有Java應用進行使用——只要客制化外部參與并將其封裝成Authentication與AuthenticationManager的進行身份驗證。
這里還需要注意的是在AuthenticationManager中實際完成身份驗證任務并不是AuthenticationManager它自己身夜涕。而是將相關的任務針對每一種身份驗證協(xié)議的AuthenticationProvider去完成相關的身份驗證工作犯犁。
問題3. 如何進行會話驗證管理?動機是什么女器?
第一個概念會話驗證是什么的一個概念酸役?我們知道HTTP的請求實際上是無狀態(tài)的,瀏覽器為了使HTTP之間能使用同一會話進行操作晓避,在Java Web中通常會將Web容器(特指Tomcat)的JSESSIONID寫入請求的cookie中一同發(fā)送簇捍。
而服務端中通過中的JSESSIONID是通過request.getSession().getId()獲取的,這樣便使本來無狀態(tài)的HTTP通過客戶端的cookie中JSESSIONID與服務端的Session關聯(lián)了起來俏拱。
但是從安全角度來說這樣匹配機制存在許多問題暑塑,最簡單的問題就是Session id在整個會話失效之間是不會變更的,這樣就可以通過身份驗證通過后獲取了Session id從而通過其他客戶端偽造cookie與服務端進行交互锅必。有興趣的同學可以針對這問題去了解下客戶端cookie事格、服務端session以及一些CSRF攻擊的介紹。本人比較推薦這篇http://hengyunabc.github.io/slides/cookie-and-session-and-csrf.html#1搞隐。
針對不同的會話管理策略場景驹愚,Security也提供了相應的實現(xiàn),有機會再單獨開一篇單獨來介紹相關的策略劣纲。這邊就先了解下逢捺,在完成了AuthenticationManager的身份驗證后,還會對其進行必要的會話驗證癞季。
問題4. 成功和失敗的后續(xù)流程都在干什么劫瞳?
驗證成功之后AuthenticationManager會返回一個通過UserDetail構造并且附帶上了所有授權信息的Authentication對象。
而驗證失敗的話則會拋出绷柒,AbstractAuthenticationProcessingFilter捕獲異常之后進行進行驗證失敗的處理志于。
成功的后續(xù)操作最主要的一個操作便是,通過SecurityContextHolder將本次驗證之后的Authentication對象塞到當前的SecurityContext中废睦。在后續(xù)的操作中如需要使用到Authentication身份信息伺绽,則可以直接通過SecurityContextHolder去獲取。
//成功后設置上下文二
SecurityContextHolder.getContext().setAuthentication(authResult);
//后續(xù)操作可以從上下文中獲取身份信息
SecurityContextHolder.getContext().getAuthentication();
然后再通過ApplicationEventPublisher發(fā)送驗證成功的事件信息供其他相關監(jiān)聽器進行相關操作嗜湃。
操作失敗就簡單了奈应,既然成功是重新將最新的Authentication對象塞到SecurityContext上下文中,失敗便是直接清空了上下文购披,讓其Authentication對象變得“一無所有”钥组。
而其他的對于request的額外操作則可以分別通過與
兩個接口去設置相關操作。
那么哪些工作屬于驗證成功后還需要額外操作的呢今瀑?舉個最簡單的例子程梦,在用戶想訪問一個受限的資源点把,他首先被重定向掉了登錄頁面讓其輸入用戶名和密碼,而在其驗證成功之后屿附,那么他是講指向到指定的某一個頁面還是重定向到本次操作本來想訪問的受限資源的路徑呢郎逃?
這些相關的操作便是在AuthenticationSuccessHandler中進行完成的。
同樣的如果登錄失敗需要做一些除了身份驗證以外挺份,有需要感知HTTP請求褒翰、響應對象的操作,同樣的也可以在AuthenticationFailureHandler中進行完成匀泊。
問題5. Remember-Me功能實現(xiàn)流程是什么优训?
Remember-Me是指網(wǎng)站能夠在Session之間記住登錄用戶的身份,具體來說就是我成功認證一次之后在一定的時間內我可以不用再輸入用戶名和密碼進行登錄了各聘,系統(tǒng)會自動給我登錄揣非。這通常是通過服務端發(fā)送一個cookie給客戶端瀏覽器,下次瀏覽器再訪問服務端時服務端能夠自動檢測客戶端的cookie躲因,根據(jù)cookie值觸發(fā)自動登錄操作早敬。
實現(xiàn)方式有很多種,一般來說最簡單的實現(xiàn)就是將用戶名與一些其他字符組合進行編碼大脉,然后服務端解碼之后提取出相關其中的用戶名搞监,通過UserDetailsService獲取相關用戶信息的用戶驗證方式。
Spring Security中提供了兩種Remember-Me機制進行使用镰矿,如果有其他實現(xiàn)方式也可以通過繼承AbstractRememberMeServices類進行擴展琐驴。請一定牢記Remember-Me機制的現(xiàn)實是依賴瀏覽器Cookie的,在默認情況下SpringSecurity會將編碼后的字符串存于Cookie中的remember-me鍵位秤标。
問題6. 如何在其他服務中監(jiān)聽驗證成功的事件
在完整整個驗證流程之后绝淡,AbstractAuthenticationProcessingFilter還會通過Spring容器的事件發(fā)布器發(fā)射一個
。如果需要在應用其他監(jiān)聽器上處理相關驗證成功后操作抛杨。我們可以通過Spring中的@EventListener監(jiān)聽InteractiveAuthenticationSuccessEvent事件便可以實現(xiàn)够委。
寫一個示例荐类,如果我們想在每個用戶登錄成功后怖现,在控制臺打印出登錄用戶的用戶名。
@Component
public class AuthenticationListener {
@EventListener
public void register(InteractiveAuthenticationSuccessEvent event)
{
//獲取登錄成功的Authentication對象
Authentication authentication = event.getAuthentication();
//打印用戶名
System.out.println("@EventListener注冊信息玉罐,用戶名:"+authentication.getName());
}
}
結尾
本期花了很大的篇幅介紹了整個Web驗證流程的核心組件AbstractAuthenticationProcessingFilter屈嗤。下一期我們將結合他最常用的實現(xiàn)類UsernamePasswordAuthenticationFilter做一個講解,希望通過講解UsernamePasswordAuthenticationFilter實現(xiàn)使大家了解客制化一個驗證協(xié)議需要注意的細節(jié)吊输。
我們下期再見饶号。