覃蘢偉 蘭全祥
Spring Security中設計模式的運用淺析
覃蘢偉 蘭全祥
(攀枝花學院,四川 攀枝花 617000)
Spring Security作為Web開發(fā)中十分重要的安全框架之一,常被用于Web應用的認證和授權。為了進一步了解Spring Security框架的設計和實現,加深對常見設計模式的理解,文章詳細介紹了Spring Security框架中策略模式、代理模式、適配器模式、責任鏈模式、模板方法模式的運用,對上述設計模式的概念、基本原理、作用等進行描述,分析Spring Security中關鍵類庫在設計模式中承擔的作用及執(zhí)行流程,為開發(fā)人員提供一定的學習參考。
Spring Security;設計模式;認證;授權
在Web應用開發(fā)過程中,系統(tǒng)安全性和數據安全性是開發(fā)人員必須要考慮的,而Spring Security就是一個為企業(yè)應用系統(tǒng)提供訪問控制的安全框架。該框架的目的是在系統(tǒng)開發(fā)中減少編寫大量重復的安全控制方面的代碼,提高程序開發(fā)效率[1]。
在程序開發(fā)過程中,經過長時間的實踐,開發(fā)人員總結出了面臨一般問題的解決方案,即設計模式。研究Spring Security框架中的設計模式,不僅能夠進一步了解設計模式在開發(fā)中的運用,還能深入了解Spring Security框架的安全認證機制,能夠為框架和設計模式的學習者提供新的學習思路和學習參考。針對Spring Security框架的主要作用以及常用的設計模式,本文主要討論Spring Security框架中的策略模式、代理模式、適配器模式、責任鏈模式和模板方法模式的運用。
Spring Security作為應用程序開發(fā)中常用的安全框架,其最大的優(yōu)勢是能夠便捷、高效地完成認證和授權,且該框架能夠為開發(fā)者提供一個高度自定義的開發(fā)環(huán)境,開發(fā)者可以根據不同需求靈活、便捷地進行配置。
Spring Security中的認證是由AuthenticationManager接口來負責的,該接口的核心方法是Authentication authenticate (Authentication authentication)throws AuthenticationException,當正常返回 Authentication 時,表示認證成功;當拋出異常時,則表示認證失敗。
該接口的主要實現類為 ProviderManager,在該實現類中管理了眾多AuthenticationProvider實例。在一次完整的認證流程中,Spring Security允許存在多個AuthenticationProvider用來實現多種不同的認證方式,且都由 ProviderManager 進行統(tǒng)一管理,認證信息將被Authentication 的實現類進行保存,且用戶信息將被Spring Security的SecurityContextHolder對象進行保存。
在 Spring Security 的授權體系中,有兩個關鍵接口分別是AccessDecisionManager和AccessDecisionVoter。
當用戶請求一個資源(通常是一個接口或者方法)時,用戶的角色信息會被封裝成一個ConfigAttribute 對象。使用ConfigAttribute對象的getAttribute()方法可以獲得一個帶有“ROLE_”前綴的用戶角色信息。AccessDecisionVoter會根據用戶角色信息和所請求資源的 ConfigAttribute進行比較,進而投出贊成、反對或者棄權票。AccessDecisionManager作為訪問決策管理器,最終將決定用戶的此次訪問是否被允許。
Spring Security的核心原理是將若干個過濾器組成過濾器鏈,然后所有的Servlet請求經過這些過濾器進行認證、授權等,從而保障程序的安全性。主要過濾器包括Username Password Authentication Filter過濾器(處理表單認證信息)、Basic Authentication Filter過濾器(檢測和處理http basic認證)、Other Security Filter過濾器、Exception Translation Filter過濾器(處理Access Denied Exception和Authentication Exception異常)和Filter Security Interceptor過濾器(授權并決定是否可以訪問資源服務)等[2],Spring Security主要過濾器如圖1所示。
圖1 Spring Security主要過濾器
Spring Security框架運用了諸多設計模式,包括策略模式、代理模式、適配器模式、模板方法模式、責任鏈模式、建造者模式、觀察者模式、裝飾器模式等。本文將重點介紹策略模式、代理模式、適配器模式、責任鏈模式和模板方法模式在Spring Security中的運用。
2.1.1 策略模式介紹
策略模式是一種行為型模式,它將對象和行為分開,將行為定義為一個接口,并將其作為抽象策略類(Strategy)。每一個不同的具體行為需要實現抽象策略類,并將其作為具體策略類(ConcreteStrategy),即不同的策略。除此之外,再由上下文類(Context)根據需求對不同的策略進行選擇和替換[3]。
策略模式關注的是行為的選擇,即可以在不同的行為之間進行替換。例如,在一個業(yè)務處理流程中有許多種方式可以完成該業(yè)務,這些方式之間的區(qū)別僅在于解決方式不同,即不同的行為,可以使用策略模式讓上下文類(Context)動態(tài)地選擇一種方式進行執(zhí)行。
2.1.2 在Spring Security中的運用
一般情況下,當用戶登錄成功后,系統(tǒng)會保存用戶登錄信息。從信息安全的角度出發(fā),為了確保用戶信息只有當前線程能夠訪問,通常會將當前用戶信息和當前線程進行綁定,即將用戶信息存儲在當前線程中。但是系統(tǒng)中難免會出現多線程的業(yè)務,或是在某個具體的業(yè)務邏輯處理中需要開啟子線程,此時就需要每個子線程都能夠訪問用戶信息。這時,就出現了不同的用戶信息存儲策略,需要開發(fā)人員根據不同的需求選擇存儲策略。
Spring Security在解決用戶信息存儲的問題上采用了策略模式。該框架定義了SecurityContextHolderStrategy接口作為抽象策略類,并實現了三種不同的具體存放策略以及作為上下文類的SecurityContextHolder,如圖2所示。
圖2 Spring Security中策略模式示意圖
Spring Security在SecurityContextHolder中定義了三個靜態(tài)常量,分別是MODE_THREADLOCAL、MODE_INHERITABLETHREADLOCAL、MODE_GLOBAL,并讓其與某一具體存放策略進行對應,開發(fā)人員在開發(fā)過程中只需根據具體需求去選擇存放策略。
以MODE_THREADLOCAL所對應的存儲策略為例,程序在啟動過程中會先加載寫有該策略信息的配置文件,然后再運行過程中根據所選策略來執(zhí)行ThreadLocalSecurityContextHolderStrategy中的具體實現,該策略會通過ThreadLocal保存用戶信息。由于使用 ThreadLocal 創(chuàng)建的變量只能被當前線程訪問,不能被其他線程訪問和修改,從而確保了用戶信息安全。
2.2.1 代理模式介紹
代理模式是一種對象結構型模式,它給某一個真實對象(委托類)提供一個代理對象(代理類)。為了確保代理類能夠實現對委托類的代理,一般情況下需要兩個類共同實現公共接口,并由代理對象控制對真實對象的引用,從而控制對象的訪問[4]。
代理類能夠對委托類進行功能增強(包括功能的預處理和后置處理),由于代理類對象本身并不真正實現服務,因此代理類對象需要與對應的委托類對象進行關聯(lián),當需要提供服務時,由代理類對象調用所關聯(lián)的委托類對象的相關方法來提供服務。代理模式就是在訪問委托類對象時增加了一定程度的間接性,這樣在程序開發(fā)過程中,可以附加多種其他操作,如Spring Security框架中對密碼進行加密、加密方法的判斷和選擇等。
2.2.2 在Spring Security中的運用
為了保證用戶密碼安全,系統(tǒng)通常不會將用戶密碼以明文的方式存放在數據庫中,而是采用合適的加密方案對密碼進行加密后再進行存儲。Spring Security中定義一個PasswordEncoder接口,并提供了多個實現類來對應不同的加密方案,方便開發(fā)者對密碼進行加密,如圖3所示。
圖3 PasswordEncoder的接口及實現類
但是在系統(tǒng)運行過程中,如果長期使用單一的密碼加密方案可能會降低系統(tǒng)安全性,因此在開發(fā)過程中往往會在上述加密方案中進行隨機選擇,讓數據庫中存放不同加密方案所加密的密文,以保障用戶信息的安全。由于用戶登錄時需要進行密碼比對,且登錄和注冊時需要使用相同的PasswordEncoder接口實現類,因此多種加密方案會對登錄校驗造成困難,即在登錄校驗時需要先判斷加密方案,然后再調用對應加密方案的PasswordEncoder實現類去進行密碼比對。
為了解決上述問題,Spring Security采用代理模式對需要進行比對的密碼進行預處理,即判斷用戶密碼使用的加密方案,再進行匹配。以BCryptPasswordEncoder加密方案為例,Spring Security定義了一個代理類DelegatingePasswordEncoder,該代理類實現了PasswordEncoder接口,然后在類中調用了委托類BCryptPasswordEncoder的matches方法,如圖4所示。
圖4 DelegatingePasswordEncode代理示意圖
具體代理流程:在委托類BCryptPasswordEncoder的matches方法中先對參數中的密文進行解析,從而判斷出密碼的加密方案id。然后,根據加密方案id生成對應的加密方案對象(PasswordEncoder實現類對象),再通過調用該對象的matches方法完成密碼比對。核心代碼如下:
public boolean matches(CharSequence rawPassword, String prefixEncodedPassword) {
if (rawPassword == null && prefixEncodedPassword == null) { return true; } else {
String id = this.extractId(prefixEncodedPassword);
PasswordEncoder delegate = (PasswordEncoder)this. idToPasswordEncoder.get(id);
if (delegate == null) {
return this.defaultPasswordEncoderForMatches.matches(rawPassword, prefixEncodedPassword);
} else {
String encodedPassword = this.extractEncodedPassword (prefixEncodedPassword);
return delegate.matches(rawPassword, encodedPassword);}}}
若系統(tǒng)中存在多種不同的密碼加密方案,采用代理模式可以在進行密碼對比時對相應方法進行增強,提前對密文和加密方案進行判斷,提高開發(fā)效率。Spring Security將對PasswordEncoder各個實現類的訪問操作交給代理對象DelegatingePasswordEncoder進行,實現對不同加密方案的統(tǒng)一管理。
此外,Spring Security框架使用DelegatingFilterProxy將過濾器鏈鏈接到Web Filter中也是典型的代理模式。
2.3.1 適配器模式介紹
適配器模式屬于結構型模式,一般用作兩個不兼容接口之間的橋梁。適配器模式主要包含Target(目標抽象類,包含用戶所期望的接口)、Adaptee(被適配類,即被適配的角色)以及Adapter(適配器類)三個角色。雖然Adaptee中已經存在滿足客戶需求的內容,但是由于接口不匹配,因此需要通過Adapter對接口進行轉換,即通過Adapter使Adaptee和Target進行適配。
適配器模式的原理是讓Adapter實現目標抽象類Target并關聯(lián)一個被適配類Adaptee對象,使得后二者產生聯(lián)系。由于Adapter結合了兩個接口的功能,因此該模式能夠將一個接口轉換成客戶希望的另一個接口,使接口不兼容的那些類可以一起工作[5]。
2.3.2 在Spring Security中的運用
在Spring Security中SecurityConfigurer是一個非常重要的角色,SecurityConfigurer主要用來對Spring Security中的過濾器進行配置,從而實現對所有請求的控制。SecurityConfigurer具有一個重要的實現類WebSecurityConfigurer,該類目的是配置WebSecurity。然而,為了實現對WebSecurity的配置和初始化,需要使用HttpSecurity類的方法,即WebSecurityConfigurer這個目標抽象類需要使用HttpSecurity中的內容,但由于兩者之間不能直接進行交互,因此引入了WebSecurityConfigerAdapter作為適配器,以使兩者能夠在一起工作。
Spring Security采用適配器模式,定義了一個WebSecurityConfigerAdapter適配器(適配器類),該類繼承WebSecurityConfigurer(目標抽象類)并與HttpSecurity(適配者類)進行關聯(lián),三者關系如圖5所示。
圖5 Spring Security中適配器模式關系示意圖
在WebSecurityConfigerAdapter中能夠對HttpSecurity類的示例進行初始化,同時實現對WebSecurity的配置,進而完成WebSecurityConfiger所需業(yè)務需求,關鍵代碼如下。
public abstract class WebSecurityConfigurerAdapter implements WebSecurityConfigurer
/*省略其他成員變量*/
private HttpSecurity http;
protected final HttpSecurity getHttp() throws Exception {}//實現對HttpSecurity的初始化
public void init(WebSecurity web) throws Exception {
HttpSecurity http = this.getHttp();
//使用http完成WebSecurityConfiger所需業(yè)務
}
2.4.1 責任鏈模式介紹
責任鏈模式屬于行為型模式,它將命令的發(fā)出者和執(zhí)行者解耦。命令的發(fā)出者將命令發(fā)出后,命令到達責任鏈,并沿著責任鏈在不同的執(zhí)行者之間傳遞,最后由適當的執(zhí)行者進行處理。發(fā)出者無需關心命令由哪一個執(zhí)行者處理以及處理細節(jié),只需將命令發(fā)送到責任鏈上,實現將命令發(fā)出者和執(zhí)行者進行分離,從而降低了他們之間的耦合度。
當有多個執(zhí)行者可以處理同一個命令且無法確定最終會由哪一個或多個執(zhí)行者來進行處理時,在責任鏈模式中,這些執(zhí)行者將實現一個公共的Handler接口,并在該接口中定義了處理命令的方法和調用下一個處理者的方法。
2.4.2 在Spring Security中的運用
在Spring Security中使用了諸多的過濾器來對請求進行處理,這些過濾器都實現了一個公共的接口Filter,并在不同的實現類中重寫doFilter方法進行不同功能的實現,如圖6所示。
在進行用戶登錄認證時,使用UsernamePasswordAuthenticationFilter 過濾器對表單攜帶的登錄信息進行處理;在防止CSRF(Cross Site Request Forgery,跨域請求偽造)時,使用CsrfFilter過濾器對所有post請求進行驗證;當系統(tǒng)沒有在配置文件中指定登錄頁面時,DefaultLoginPageGeneratingFilter過濾器會為系統(tǒng)生成一個默認的登錄界面。但當一個請求到達前,系統(tǒng)并不能提前預知應該使用哪一個過濾器對請求進行處理,因此Spring Security引入了過濾器鏈來解決這一問題。
圖6 Spring Security中Filter接口實現示意圖
Spring Security采用責任鏈模式,定義了一個VirtualFilterChain 類,在其中聲明了 5 個全局屬性:originalChain 表示原生過濾器鏈(即Web Filter),additionalFilters 表示 Spring Security 中的過濾器鏈(存儲Filter的list集合);firewalledRequest 表示當前請求;size 表示過濾器鏈中過濾器的個數;currentPosition 則是遍歷過濾器鏈時的下標。
當currentPosition等于size時,表示過濾器鏈已經執(zhí)行完畢,此時會退出 Spring Security 過濾器鏈,并通過調用 originalChain.doFilter 進入到原生過濾鏈方法中。反之,則依次從 additionalFilters 中取出過濾器nextFilter并調用其doFilter 方法。相關代碼如下:
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
if (this.currentPosition == this.size) {
this.originalChain.doFilter(request, response);
} else {
++this.currentPosition;
Filter nextFilter = (Filter)this.additionalFilters.get (this.currentPosition - 1);
nextFilter.doFilter(request, response, this);
}}
Spring Security中運用責任鏈模式,保障過濾器鏈中的每個過濾器都具有不同的職能并且互不相擾,使得每個過濾器都能獨立、逐個處理Servlet請求。
2.5.1 模板方法模式介紹
模板方法模式是一種行為型模式,將一個業(yè)務邏輯中的方法骨架作為一個模板方法,并將某些方法的具體實現延遲到子類中完成,使子類可以在不改變該業(yè)務邏輯執(zhí)行順序的情況下更改某些方法的實現。
模板方法模式中的主要角色有抽象類(Abstract Class),該類負責給出算法的輪廓和骨架。抽象類由一個模板方法和若干基礎方法構成,其中模板方法就是算法的骨架,并按照某種順序去調用基礎方法。當存在多個業(yè)務具有相同邏輯,且業(yè)務處理順序基本一致時,可以考慮模板方法模式。
2.5.2 在Spring Security中的運用
在Spring Security中諸多功能都是基于過濾器鏈實現的,且這些過濾器鏈都是通過建造者類來進行配置并創(chuàng)建的,工作流程都十分固定,主要包括初始化(init)、配置(config)、構建(performBuild)。但由于這些過濾器鏈的方法內部的實現都不盡相同,因此Spring Security通過模板方法來解決這一問題。
Spring Security采用模板方法模式定義了一個AbstractConfiguredSecurityBuilder作為所有過濾器鏈建造者類的父類,并在父類中規(guī)定了工作流程doBuild()方法(即方法調用順序),并使用final修飾該方法,使得子類無法改變,從而讓所有的過濾器鏈都按照前置初始化(beforeInit)、初始化(init)、前置配置(beforeConfigure)、配置(Configure)、構建(performBuild)的順序執(zhí)行建造者類中的方法。然后,這些過濾器鏈可以根據實際需求重寫performBuild()方法,關鍵代碼如下。
protected final O doBuild() throws Exception {
this.beforeInit();
this.init();
this.beforeConfigure();
this.configure();
O result = this.performBuild();
return result;
}
private void init() throws Exception { }
private void configure() throws Exception {}
protected abstract O performBuild() throws Exception{}
Spring Security中采用模板方法模式,將創(chuàng)建過濾器鏈的工作流程交由父類控制,而行為的實現(performBuild()方法)延遲到子類當中,使得不同子類可以完成不同的實現。
設計模式的意義就是為程序開發(fā)提供解決方案,優(yōu)化代碼邏輯,使程序更易于維護、擴展和服用。以Spring Security框架為例,理解框架中所運用到的策略模式、代理模式、適配器模式、責任鏈模式、模板方法模式等設計模式,不僅能夠從主流框架中學習到程序設計和開發(fā)思想,關注信息系統(tǒng)安全性,考慮工程實踐對于社會的影響,還能夠進一步了解設計模式的優(yōu)缺點及使用場景。理解、掌握設計模式原理,并能夠在合適的場景中使用設計模式是提高開發(fā)效率,增強代碼可維護性的主要途經之一。
[1] 孫恩斯. Spring Security安全框架應用研究[J]. 信息系統(tǒng)工程,2019(3): 72.
[2] 朱運喬. 基于Spring Security認證與授權的Web應用與實現[J]. 電腦編程技巧與維護,2020(11): 14-16.
[3] 許俊. 基于策略模式的存儲過程使用研究[J]. 四川職業(yè)技術學院學報,2017,27(5): 147-150.
[4] 盧增寧. 設計模式及其在軟件設計中的應用[J]. 信息與電腦(理論版),2020,32(16): 127-129.
[5] 高升,方英蘭,韓兵,等. 適配器與裝飾者模式思想在結構化數據處理中的應用[J]. 北方工業(yè)大學學報,2020,32(2): 105-109,116.
Analysis of the Application of Design Patterns in Spring Security
As one of the most important security frameworks for Web development, Spring Security is often used for authentication and authorization of Web applications. In order to further understand the design and implementation of the Spring Security framework and deepen the understanding of common design patterns, this paper introduces the application of policy pattern, proxy pattern, adapter pattern, responsibility chain pattern and template method pattern in Spring Security framework in detail. This paper describes the concept, basic principle and function of the above design pattern, analyzes the role of key class libraries in Spring Security in the design pattern and the execution process, and provides certain reference for developers.
Spring Security; design patterns; authentication; authorization
TP393.08
A
1008-1151(2023)09-0001-05
2022-11-25
四川省高等學校第四批省級創(chuàng)新創(chuàng)業(yè)教育示范課程建設項目“Java Web應用開發(fā)實訓”;2022年全國大學生創(chuàng)新創(chuàng)業(yè)訓練計劃項目(202211360019);2022年校級大學生創(chuàng)新創(chuàng)業(yè)訓練計劃項目(2022cxcy147)。
覃蘢偉(2002-),男,四川自貢人,攀枝花學院學生,研究方向為軟件開發(fā)。
蘭全祥(1990-),男,四川攀枝花人,攀枝花學院講師,碩士,從事計算機應用研究工作。