盤點 Spring Security 框架中的八大經典設計模式

上次有小夥伴建議,源碼分析太枯燥了,要是可以結合設計模式一塊兒來,這樣更有助於你們理解 Spring Security 源碼,同時還能複習一波設計模式。java

所以鬆哥今天就試着整一篇,和你們來聊一聊 Spring Security 中涉及到的設計模式,不過 Spring Security 中涉及到的設計模式仍是很是多的,鬆哥這裏講幾個,剩下的歡迎小夥伴們留言補充。算法

1.模板方法模式

Template Pattern(模板方法模式)是一個抽象類公開定義了執行它的方法的模板。它的子類能夠按須要重寫方法實現,但調用將以抽象類中定義的方式進行,這是一種行爲型模式。

模板方法方式優勢以下:spring

  • 在父類中提取了公共的部分代碼,便於代碼複用和擴展。
  • 部分方法是由子類實現的,子類能夠經過擴展方式增長相應的功能,符合開閉原則。

缺點以下:數據庫

  • 對每一個不一樣的實現都須要定義一個子類,致使類的個數增長,系統更加複雜,設計也更加抽象。
  • 父類中的抽象方法由子類實現,子類執行的結果會影響父類的結果,增長了代碼理解難度。

介紹完模板方法模式,你們可能大概猜到了 Spring Security 中哪些地方用到模板方法模式了。設計模式

我舉幾個簡單的例子。session

第一個例子是 AbstractUserDetailsAuthenticationProvider 類的設計。你們都知道這個類是用來作驗證的,認證的邏輯在這個方法中都定義好了,可是該類卻定義了兩個抽象方法:併發

  • retrieveUser 該方法用戶從數據源中獲取用戶對象。
  • additionalAuthenticationChecks 該方法用來作額外的校驗(登陸憑證的校驗)

這兩個抽象方法是在 DaoAuthenticationProvider 中實現的。DaoAuthenticationProvider 的實現就是從數據庫中加載用戶,默認檢驗登陸憑證也都是驗證密碼。app

若是你的數據源來自其餘地方,或者登陸憑證不是密碼,那麼自定義類繼承自 AbstractUserDetailsAuthenticationProvider 並重寫它裏邊的這兩個方法便可。框架

2.責任鏈模式

Chain of Responsibility Pattern(責任鏈模式) ,在這種模式中,一般每一個接收者都包含對另外一個接收者的引用,若是一個對象不能處理該請求,那麼它會把相同的請求傳給下一個接收者,依此類推。在這個過程當中,客戶只須要將請求發送到責任鏈上便可,無須關心請求的處理細節和請求的傳遞過程,因此責任鏈將請求的發送者和請求的處理者解耦了。

責任鏈模式優勢以下:ide

  • 下降對象之間的耦合度。
  • 加強了系統的可擴展性。
  • 當工做流程發生變化,能夠動態地改變鏈內的成員或者調動它們的次序。
  • 簡化了對象之間的鏈接,每一個對象只需保持一個指向其後繼者的引用,不需保持其餘全部處理者的引用。
  • 責任分擔,每一個類只須要處理本身該處理的工做,符合類的單一職責原則。

缺點以下:

  • 對比較長的職責鏈,請求的處理可能涉及多個處理對象,系統性能將受到必定影響。
  • 職責鏈創建的合理性要靠客戶端來保證,增長了客戶端的複雜性。

很明顯,Spring Security 中的過濾器鏈就是一種責任鏈模式。一個請求到達後,被過濾器鏈中的過濾器逐個進行處理,過濾器鏈中的過濾器每一個都具備不一樣的職能而且互不相擾,咱們還能夠經過 HttpSecurity 來動態配置過濾器鏈中的過濾器(即添加/刪除過濾器鏈中的過濾器)。

具體的代碼在 FilterChainProxy$VirtualFilterChain 中,以下:

那麼接下來咱們就來看看 VirtualFilterChain:

private static class VirtualFilterChain implements FilterChain {
    private final FilterChain originalChain;
    private final List<Filter> additionalFilters;
    private final FirewalledRequest firewalledRequest;
    private final int size;
    private int currentPosition = 0;
    private VirtualFilterChain(FirewalledRequest firewalledRequest,
            FilterChain chain, List<Filter> additionalFilters) {
        this.originalChain = chain;
        this.additionalFilters = additionalFilters;
        this.size = additionalFilters.size();
        this.firewalledRequest = firewalledRequest;
    }
    @Override
    public void doFilter(ServletRequest request, ServletResponse response)
            throws IOException, ServletException {
        if (currentPosition == size) {
            if (logger.isDebugEnabled()) {
                logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
                        + " reached end of additional filter chain; proceeding with original chain");
            }
            // Deactivate path stripping as we exit the security filter chain
            this.firewalledRequest.reset();
            originalChain.doFilter(request, response);
        }
        else {
            currentPosition++;
            Filter nextFilter = additionalFilters.get(currentPosition - 1);
            if (logger.isDebugEnabled()) {
                logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
                        + " at position " + currentPosition + " of " + size
                        + " in additional filter chain; firing Filter: '"
                        + nextFilter.getClass().getSimpleName() + "'");
            }
            nextFilter.doFilter(request, response, this);
        }
    }
}
  1. VirtualFilterChain 類中首先聲明瞭 5 個全局屬性,originalChain 表示原生的過濾器鏈,也就是 Web Filter;additionalFilters 表示 Spring Security 中的過濾器鏈;firewalledRequest 表示當前請求;size 表示過濾器鏈中過濾器的個數;currentPosition 則是過濾器鏈遍歷時候的下標。
  2. doFilter 方法就是 Spring Security 中過濾器挨個執行的過程,若是 currentPosition == size,表示過濾器鏈已經執行完畢,此時經過調用 originalChain.doFilter 進入到原生過濾鏈方法中,同時也退出了 Spring Security 過濾器鏈。不然就從 additionalFilters 取出 Spring Security 過濾器鏈中的一個個過濾器,挨個調用 doFilter 方法。nextFilter.doFilter 就是過濾器鏈挨個往下走。

關於 FilterChainProxy 的介紹,參見:深刻理解 FilterChainProxy【源碼篇】

3.策略模式

Strategy Pattern(策略模式),它定義了一系列算法,將每個算法封裝起來,並讓它們能夠相互替換。策略模式讓算法獨立於使用它的客戶而變化,也稱爲政策模式(Policy)。

策略模式的優勢:

  • 策略模式提供了對「開閉原則」的完美支持,用戶能夠在不修改原有系統的基礎上選擇具體的策略,也能夠靈活地擴展新的策略。
  • 策略模式提供了管理相關的策略的方式。
  • 策略模式提供了能夠替換繼承關係的辦法。
  • 使用策略模式能夠避免使用多重條件轉移語句。

策略模式的缺點:

  • 客戶端必須知道全部的策略類,並自行決定使用哪個策略類。
  • 策略模式將形成產生不少策略類(能夠經過使用享元模式在必定程度上減小對象的數量)。

Spring Security 中使用策略模式的地方也有好幾個。

第一個就是用戶登陸信息存儲。

在 SecurityContextHolder 中定義登陸用戶信息存儲的方法,就定義了三種不一樣的策略:

public class SecurityContextHolder {
    // ~ Static fields/initializers
    // =====================================================================================

    public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL";
    public static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL";
    public static final String MODE_GLOBAL = "MODE_GLOBAL";
    public static final String SYSTEM_PROPERTY = "spring.security.strategy";
    private static String strategyName = System.getProperty(SYSTEM_PROPERTY);
    private static SecurityContextHolderStrategy strategy;
}

用戶能夠自行選擇使用哪種策略!具體參見:在 Spring Security 中,我就想從子線程獲取用戶登陸信息,怎麼辦?

還有一個就是 session 併發管理。

在 AbstractAuthenticationProcessingFilter#doFilter 方法中,有以下代碼:

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
        throws IOException, ServletException {
    //省略
        sessionStrategy.onAuthentication(authResult, request, response);
    //省略
}

這就是一種策略模式。

Session 併發管理能夠參考:

固然,這樣的例子還有不少,我就不一一列舉了。

4.代理模式

Proxy Pattern(代理模式) :給某一個對象提供一個代理,並由代理對象控制對原對象的引用,它是一種對象結構型模式。

代理模式的優勢:

  • 必定程度上下降了系統的耦合度。
  • 代理對象能夠擴展目標對象的功能。
  • 代理對象能夠保護目標對象。

缺點:

  • 在客戶端和真實對象之間增長了代理,可能會致使請求的處理速度變慢。
  • 增長了系統複雜度。

代理模式在 Spring Security 中最重要的應用就是 Spring Security 過濾器連接入 Web Filter 的過程,使用了 Spring 提供的 DelegatingFilterProxy,這就是一個典型的代理模式:

public class DelegatingFilterProxy extends GenericFilterBean {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        // Lazily initialize the delegate if necessary.
        Filter delegateToUse = this.delegate;
        if (delegateToUse == null) {
            synchronized (this.delegateMonitor) {
                delegateToUse = this.delegate;
                if (delegateToUse == null) {
                    WebApplicationContext wac = findWebApplicationContext();
                    if (wac == null) {
                        throw new IllegalStateException("No WebApplicationContext found: " +
                                "no ContextLoaderListener or DispatcherServlet registered?");
                    }
                    delegateToUse = initDelegate(wac);
                }
                this.delegate = delegateToUse;
            }
        }

        // Let the delegate perform the actual doFilter operation.
        invokeDelegate(delegateToUse, request, response, filterChain);
    }
}

固然還有其餘不少地方也用到代理模式,我就不一一列舉了,歡迎小夥伴們留言補充。

5.適配器模式

Adapter Pattern(適配器模式),你們平時用的手機充電器學名叫作電源適配器,它的做用是把 220V 的電壓轉爲手機可用的 5V 電壓。因此適配器模式其實也是相似做用,將一個接口轉換成客戶但願的另外一個接口,適配器模式使接口不兼容的類能夠一塊兒工做。適配器模式又分爲類適配器模式、對象適配器模式以及接口適配器模式。

適配器模式的優勢:

  • 解耦,經過引入一個適配器類來重用現有的適配者類,而無須修改原有代碼。
  • 增長了類的透明性和複用性。
  • 具備較好的靈活性和擴展性都。

缺點:

  • 因爲 Java 不支持多重繼承,一次最多隻能適配一個適配者類,並且目標抽象類只能爲抽象類,不能爲具體類,其使用有必定的侷限性。

Spring Security 中的適配器模式也是很是多的,例如咱們最爲常見的 WebSecurityConfigurerAdapter,該類讓兩個本來不相關的 WebSecurity 和 HttpSecurity 可以在一塊兒工做。

具體參見:[深刻理解 WebSecurityConfigurerAdapter【源碼篇】]()

6.建造者模式

Builder Pattern(建造者模式)是將一個複雜對象的構建與它的表示分離,使得一樣的構建過程能夠建立不一樣的對象出來,用戶只須要指定複雜對象的類型和內容就能夠構建對象,而不須要知道內部的具體構建細節。

建造者模式優勢:

  • 將產品自己與產品的建立過程解耦,使得相同的建立過程能夠建立不一樣的產品對象,而客戶端不須要知道產品內部細節。
  • 每個產品對應一個建造者,用戶使用不一樣的建造者能夠建立不一樣的產品,建造者自己能夠輕鬆修改或者添加。
  • 能夠更加精細地控制產品的建立過程。

缺點:

  • 建立的產品須要有必定的類似性,若是差別過大,則不適合建造者模式。
  • 產品自己的複雜度會提升建造者的複雜度。

Spring Security 中對於建造者模式的使用也是很是多,例如典型的 AuthenticationManagerBuilder,它想要建造的對象是 AuthenticationManager,對應的建造方法則是 build。通常建造者模式中建造者類命名以 builder 結尾,而建造方法命名爲 build()。

關於 AuthenticationManagerBuilder,參見:深刻理解 AuthenticationManagerBuilder 【源碼篇】 一文。

7.觀察者模式

Observer(觀察者模式)指多個對象間存在一對多的依賴關係,當一個對象的狀態發生改變時,全部依賴於它的對象都獲得通知並自動更新,觀察者模式也稱爲發佈-訂閱模式、模型-視圖模式,它是對象行爲型模式。

觀察者模式優勢:

  • 下降了目標與觀察者之間的耦合關係,二者之間是抽象耦合關係。

缺點:

  • 目標與觀察者之間的依賴關係並無徹底解除,並且有可能出現循環引用。
  • 當觀察者對象不少時,程序執行效率下降。

在 Spring 框架中,觀察者模式用於實現 ApplicationContext 的事件處理功能。Spring 爲咱們提供了 ApplicationEvent 類和 ApplicationListener 接口來啓用事件處理。Spring 應用程序中的任何 Bean 實現 ApplicationListener 接口,都會接收到 ApplicationEvent 做爲事件發佈者推送的消息。在這裏,事件發佈者是主題(Subject) 和實現 ApplicationListener 的 Bean 的觀察者(Observer)。

具體到 Spring Security 中,如登陸成功事件發佈,session 銷燬事件等等,都算是觀察者模式。

例如 AbstractAuthenticationProcessingFilter#successfulAuthentication 方法:

protected void successfulAuthentication(HttpServletRequest request,
        HttpServletResponse response, FilterChain chain, Authentication authResult)
        throws IOException, ServletException {
    if (logger.isDebugEnabled()) {
        logger.debug("Authentication success. Updating SecurityContextHolder to contain: "
                + authResult);
    }
    SecurityContextHolder.getContext().setAuthentication(authResult);
    rememberMeServices.loginSuccess(request, response, authResult);
    // Fire event
    if (this.eventPublisher != null) {
        eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
                authResult, this.getClass()));
    }
    successHandler.onAuthenticationSuccess(request, response, authResult);
}

相似還有不少,如 session 銷燬事件等(參見Spring Security 自動踢掉前一個登陸用戶,一個配置搞定!),我這裏就不一一列舉了。

8.裝飾模式

Decorator(裝飾模式)是指在不改變現有對象結構的狀況下,動態地給該對象增長一些額外功能的模式。

裝飾模式的優勢:

  • 能夠靈活的擴展一個類的功能。

缺點:

  • 增長了許多子類,使程序變得很複雜。

Spring Security 中對於裝飾模式也有許多應用。最典型的就是一個請求在經過過濾器鏈的時候會不停的變,會不停的調整它的功能,經過裝飾模式設計出了請求的許多類,例如:

  • HeaderWriterRequest
  • FirewalledRequest
  • StrictHttpFirewall
  • SaveToSessionRequestWrapper
  • ...

等等,相似的不少,我就不一一贅述了。

小結

鬆哥的 Spring Security 還在持續連載中,將來連載完了還會總結出更多的設計模式,這裏先列出來八個和小夥伴們分享,若是小夥伴們有本身的看法,也歡迎留言補充。

相關文章
相關標籤/搜索