Spring Security小教程 Vol 3. 身份驗證的入口-AbstractAuthenticationProcessingFilter

前言

結合上一期咱們介紹的AuthenticationManager爲入口的身份驗證的核心模塊,咱們此次討論的是爲了使SpringSecurity對Spring Web項目提供支持,做爲驗證請求入口的\color{red}{AbstractAuthenticationProcessingFilter}html

第三期 Authentication核心簡介

本期的任務清單

  1. AbstractAuthenticationProcessingFilter的依賴組件;
  2. AbstractAuthenticationProcessingFilter依賴組件的主要職責和相關設計動機。

1. AbstractAuthenticationProcessingFilter處理Request及與AuthenticationManager交互的流程

AbstractAuthenticationProcessingFilter的主要職責和依賴組件

瞭解AbstractAuthenticationProcessingFilter大概是幹嗎的的最簡單的方法就是直接去讀api-doc。git

Abstract processor of browser-based HTTP-based authentication requests.github

官方文檔說的很明白了:處理基於瀏覽器交互的HTTP驗證請求。因此AbstractAuthenticationProcessingFilter的職責也就很是明確——處理全部HTTP Request和Response對象,並將其封裝成AuthenticationMananger能夠處理的Authentication。而且在身份驗證成功或失敗以後將對應的行爲轉換爲HTTP的Response。同時還要處理一些Web特有的資源好比Session和Cookie。總結成一句話,就是替AuthenticationMananger把全部和Authentication不要緊的事情所有給包圓了。 web

繼續讀JavaDoc能夠得知AbstractAuthenticationProcessingFilter爲了完成組織上交代的與瀏覽器和HTTP請求的驗證任務。它將大任務拆成了幾個子任務並交給瞭如下組件完成:spring

  1. AuthenticationManager用於處理身份驗證的核心邏輯;
  2. AuthenticationSuccessHandler用於處理驗證成功的後續流程;
  3. AuthenticationFailureHandler用於處理失敗的後續流程;
  4. 在驗證成功後發佈一個名爲InteractiveAuthenticationSuccessEvent的事件通知給到應用上下文,用於告知身份驗證已經成功;
  5. 由於是基於瀏覽器因此相關的會話管理行爲交由 SessionAuthenticationStrategy來進行實現。
  6. 文檔上還有一點沒有寫出來的是,若是用戶開啓了相似「記住我」之類的免密碼登陸,AbstractAuthenticationProcessingFilter還有一個名爲RememberMeServices來進行管理。

AbstractAuthenticationProcessingFilter的驗證流程

AbstractAuthenticationProcessingFilter本質上仍是個Filter,其核心的業務入口方法就是doFilter方法: api

這裏咱們先設置一個問題,而後帶着問題去分析AbstractAuthenticationProcessingFilter的doFilter都是怎麼設計解決這些問題的?

  1. 怎麼判斷當前的請求是須要被驗證訪問的?
  2. 如何進行身份驗證?
  3. 如何進行會話驗證?動機是什麼?
  4. 成功和失敗的後續流程都在幹什麼?
  5. Remember-Me功能實現流程是什麼?
  6. 如何在其餘服務中監聽驗證成功的事件?

問題1. 怎麼判斷當前的請求是須要被驗證訪問的?

在正式進行身份以前,doFilter會經過Security中的\color{red}{RequestMatcher}。嘗試查找是否有匹配記錄。 咱們回顧下以前咱們寫過的訪問控制的代碼:瀏覽器

@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的規則便會在這個流程中預先被檢查,若是須要進行身份驗證則會進行寫一個階段:對請求進行必要的身份驗證。安全

問題2. 如何進行身份驗證?

doFilter中經過調用本身的attemptAuthentication方法,但並不進行身份驗證的邏輯處理,而是委託AuthenticationManager去完成相關的身份驗證流程。AbstractAuthenticationProcessingFilter將HttpServletRequest包裝成了Authentication對象與核心的AuthenticationManager進行交互。這樣的設計可使AuthenticationManager不感知外部的Web環境,從而使Security不只能夠支持Web應用,同時也能夠被全部Java應用進行使用——只要客製化外部參與並將其封裝成Authentication與AuthenticationManager的進行身份驗證。 bash

這裏還須要注意的是在AuthenticationManager中實際完成身份驗證任務並非AuthenticationManager它本身身。而是將相關的任務針對每一種身份驗證協議的AuthenticationProvider去完成相關的身份驗證工做。 cookie

問題3. 如何進行會話驗證管理?動機是什麼?

第一個概念會話驗證\color{red}{SessionAuthentication}是什麼的一個概念?咱們知道HTTP的請求其實是無狀態的,瀏覽器爲了使HTTP之間能使用同一會話進行操做,在Java Web中一般會將Web容器(特指Tomcat)的JSESSIONID寫入請求的cookie中一同發送。

而服務端中經過中的JSESSIONID是經過request.getSession().getId()獲取的,這樣便使原本無狀態的HTTP經過客戶端的cookie中JSESSIONID與服務端的Session關聯了起來。 可是從安全角度來講這樣匹配機制存在許多問題,最簡單的問題就是Session id在整個會話失效之間是不會變動的,這樣就能夠經過身份驗證經過後獲取了Session id從而經過其餘客戶端僞造cookie與服務端進行交互。有興趣的同窗能夠針對這問題去了解下客戶端cookie、服務端session以及一些CSRF攻擊的介紹。本人比較推薦這篇http://hengyunabc.github.io/slides/cookie-and-session-and-csrf.html#1。

針對不一樣的會話管理策略場景,Security也提供了相應的實現,有機會再單獨開一篇單獨來介紹相關的策略。這邊就先了解下,在完成了AuthenticationManager的身份驗證後,還會對其進行必要的會話驗證。

問題4. 成功和失敗的後續流程都在幹什麼?

驗證成功以後AuthenticationManager會返回一個經過UserDetail構造而且附帶上了全部受權信息的Authentication對象。 而驗證失敗的話則會拋出\color{red}{AuthenticationException},AbstractAuthenticationProcessingFilter捕獲異常以後進行進行驗證失敗的處理。 成功的後續操做最主要的一個操做即是,經過SecurityContextHolder將本次驗證以後的Authentication對象塞到當前的SecurityContext中。在後續的操做中如須要使用到Authentication身份信息,則能夠直接經過SecurityContextHolder去獲取。

//成功後設置上下文二
    SecurityContextHolder.getContext().setAuthentication(authResult);

     //後續操做能夠從上下文中獲取身份信息
     SecurityContextHolder.getContext().getAuthentication();
複製代碼

而後再經過ApplicationEventPublisher發送驗證成功的事件信息供其餘相關監聽器進行相關操做。

操做失敗就簡單了,既然成功是從新將最新的Authentication對象塞到SecurityContext上下文中,失敗即是直接清空了上下文,讓其Authentication對象變得「一無全部」。

而其餘的對於request的額外操做則能夠分別經過\color{red}{AuthenticationSuccessHandler}\color{red}{AuthenticationFailureHandler}兩個接口去設置相關操做。

那麼哪些工做屬於驗證成功後還須要額外操做的呢?舉個最簡單的例子,在用戶想訪問一個受限的資源,他首先被重定向掉了登陸頁面讓其輸入用戶名和密碼,而在其驗證成功以後,那麼他是講指向到指定的某一個頁面仍是重定向到本次操做原本想訪問的受限資源的路徑呢? 這些相關的操做即是在AuthenticationSuccessHandler中進行完成的。 一樣的若是登陸失敗須要作一些除了身份驗證之外,有須要感知HTTP請求、響應對象的操做,一樣的也能夠在AuthenticationFailureHandler中進行完成。

問題5. Remember-Me功能實現流程是什麼?

Remember-Me是指網站可以在Session之間記住登陸用戶的身份,具體來講就是我成功認證一次以後在必定的時間內我能夠不用再輸入用戶名和密碼進行登陸了,系統會自動給我登陸。這一般是經過服務端發送一個cookie給客戶端瀏覽器,下次瀏覽器再訪問服務端時服務端可以自動檢測客戶端的cookie,根據cookie值觸發自動登陸操做。 實現方式有不少種,通常來講最簡單的實現就是將用戶名與一些其餘字符組合進行編碼,而後服務端解碼以後提取出相關其中的用戶名,經過UserDetailsService獲取相關用戶信息的用戶驗證方式。 Spring Security中提供了兩種Remember-Me機制進行使用,若是有其餘實現方式也能夠經過繼承AbstractRememberMeServices類進行擴展。請必定牢記Remember-Me機制的現實是依賴瀏覽器Cookie的,在默認狀況下SpringSecurity會將編碼後的字符串存於Cookie中的remember-me鍵位。

#問題6. 如何在其餘服務中監聽驗證成功的事件

在完整整個驗證流程以後,AbstractAuthenticationProcessingFilter還會經過Spring容器的\color{red}{ApplicationEventPublisher}事件發佈器發射一個\color{red}{InteractiveAuthenticationSuccessEvent}。若是須要在應用其餘監聽器上處理相關驗證成功後操做。咱們能夠經過Spring中的@EventListener監聽InteractiveAuthenticationSuccessEvent事件即可以實現。 寫一個示例,若是咱們想在每一個用戶登陸成功後,在控制檯打印出登陸用戶的用戶名。

@Component
public class AuthenticationListener {
    @EventListener
    public void register(InteractiveAuthenticationSuccessEvent event)
    {
        //獲取登陸成功的Authentication對象
        Authentication authentication = event.getAuthentication();
        //打印用戶名
        System.out.println("@EventListener註冊信息,用戶名:"+authentication.getName());
    }
}
複製代碼

結尾

本期花了很大的篇幅介紹了整個Web驗證流程的核心組件AbstractAuthenticationProcessingFilter。下一期咱們將結合他最經常使用的實現類UsernamePasswordAuthenticationFilter作一個講解,但願經過講解UsernamePasswordAuthenticationFilter實現使你們瞭解客製化一個驗證協議須要注意的細節。 咱們下期再見。

轉載於:https://juejin.im/post/5c98b0d2e51d4543f02ca7f7