spring-boot-starter-security與應用安全

應用安全屬於安全防護體系中的重要一環,但也是最薄弱的一環,究其原因,或許是應用的核心職責是完成業務和產品的功能需求,而安全確實非功能性需求,在資源有限的情況下,企業一定是更加註重將有限的資源投入到“開疆擴土”上去,否則,窮家破瓦的,也真沒有什麼值得安全防護的。


大部分應用開發者對應用安全知之甚少,而且安全一般屬於一個企業或者業界秘而不宣的信息,所以,在沒有一個專職的安全團隊負責推動整個安全防護體系落實的情況下,零零散散和線上落實的一些應用安全防護已經算很不錯的了。
“樹大招風”,樹不大的時候,那些“風”通常也不會來找你麻煩,所以,大部分中小企業,除非應用了一些業界廣泛應用的軟件或者方案被連帶性地傷害到,大部分情況下,這些中小企業並不知道這類潛在風險,或者是他們自身不會為黑客們(cracker)帶來太大的利用價值。
好在 Java 開發者生活在 Spring 框架營造的生態圈之中,所以,關於應用安全這種頭疼的問題,Spring 生態圈裡也有現成的解決方案,即從 Acegi 發展起來的 SpringSecurity。
但是說實話,SpringSecurity 在整個社區中的名聲並不是太好,尤其是在開發者眼中,“複雜(Too Complicated),太重(Too Heavyweight)”,但實際上,如果大家真得去了解一個框架,就會發現,其實 SpringSecurity 框架本身的設計還是挺優秀的。
SpringSecurity 可以任意裁剪,而且還提供了豐富的開箱即用的安全特性支持。這裡其實存在一個常見的設計取捨,我們到底應該為了良好的擴展和組合型而將組件拆分的精細一些,還是應該為了使用的便利,適度忽略定製化的需求,提供一個功能簡化的一站式方案?

不管 SpringSecurity 團隊當時是如何選擇的,既然已經成為了事實,給使用者的感受不好,那麼,我們就要想辦法改善這種現狀,spring-boot-starter-security 就是一種答案。
spring-boot-starter-security 主要面向 Web 應用安全,配合 spring-boot-starter-web,要使用 SpringBoot 構建一個安全的對外提供服務的 Web 應用簡直太容易了,代碼如下所示:

<code><project>    <modelversion>4.0.0/<modelversion>    <groupid>com.keevol.unveilspring.chapter3/<groupid>    <artifactid>web-security-demo/<artifactid>    <version>0.0.1-SNAPSHOT/<version>    <packaging>jar/<packaging>    <name>web-security-demo/<name>    <description>web security demo project for Spring Boot/<description>    <parent>        <groupid>org.springframework.boot/<groupid>        <artifactid>spring-boot-starter-parent/<artifactid>        <version>1.3.1.RELEASE/<version>        <relativepath>     /<parent>    <properties>        <project.build.sourceencoding>UTF-8/<project.build.sourceencoding>        <java.version>1.8/<java.version>    /<properties>    <dependencies>        <dependency>            <groupid>org.springframework.boot/<groupid>            <artifactid>spring-boot-starter-security/<artifactid>        /<dependency>        <dependency>            <groupid>org.springframework.boot/<groupid>            <artifactid>spring-boot-starter-web/<artifactid>        /<dependency>     /<dependencies>    <build>        <plugins>            <plugin>                <groupid>org.springframework.boot/<groupid>                <artifactid>spring-boot-maven-plugin/<artifactid>            /<plugin>        /<plugins>    /<build>/<project> /<code>

在當前項目中只要添加需要的 Controller 實現,一個添加了基本安全防護的 Web 應用就誕生了。spring-boot-starter-security 默認會提供一個基於 HTTP Basic 認證的安全防護策略,默認用戶名為 user,訪問密碼則在當前 Web 應用啟動的時候,打印到控制檯,類似於:

2017-01-01 13:57:00.596 INFO 17966 --- [ost-startStop-1] b.a.s.Au-thenticationManagerConfiguration : Using default security password: 560ff91b-0ae7-492c-ad16-603e1adec54c

如果我們希望對 HTTP Basic 認證的用戶名和密碼進行定製,可以通過如下配置項進行:

security.user.name={個人希望設置的用戶名}security.user.password={個人希望使用的訪問密碼}

除此之外,spring-boot-starter-security 還會默認啟用一些必要的 Web 安全防護,比如針對 XSS、CSRF 等常見針對 Web 應用的攻擊,同時,也會將一些常見的靜態資源路徑排除在安全防護之外。
但是,說實話,spring-boot-starter-security 提供的默認安全策略相對於真正的生產環境來說,還是太弱了。但也沒辦法,既要安全,又要便利,spring-boot-starter-security 默認情況下已經儘量做到夠好了。


不過好在 SpringSecurity 擴展性不錯,要在其上構建一套真正嚴謹有效的 Web 應用安全防護體系也並非難事,只不過,需要我們先能夠從其架構設計上理解並把握它,然後再在 SpringSecurity 和 SpringBoot 的基礎上構建一套符合自身需要的 Web 應用安全方案。

瞭解 SpringSecurity 基本設計

SpringSecurity 框架不但囊括了基本的認證和授權功能,而且還提供了加密解密、統一登錄等一系列相關支持,本教程中只是對框架比較核心的設計進行簡單介紹,即基本的認證和授權為核心的設計。
我們可以將 Spring Security 的幾個核心概念按照圖 1 所示勾勒在一起:

spring-boot-starter-security與應用安全

圖 1 SpringSecurity 核心概念示意圖


訪問者(Accessor)需要訪問某個資源(Resources)是這個場景中最原始的需求,但並不是誰都可以訪問資源,也不是任何資源都允許任何人來訪問,所以,中間我們要加入一些檢查和防護。
在訪問資源的所經之路上,可能需要上山、過橋、下海行船,不管怎麼樣,這些所經之路對於我們要防護的資源來說都是比較好的設置關卡點,對應上圖就是 FilterInvocation(對應 Web 應用場景)、MethodInvocation 以及 Joinpoint,這些在 Spring Security 框架中統稱 Secured Object(s)。
我們知道了在哪裡設置關卡最合適,下一步就是設置關卡,對應不同的所經之路,我們分別設置類似 FilterSecurityInterceptor、Method-SecurityInterceptor 以及 AspectJSecurityInterceptor 這樣的關卡來負責攔截非法資源訪問的闖入者們。
而在 Spring Security 框架的設計中,關卡的概念統一抽象為 AbstractSecurityInterceptor,而 FilterSecurity-Interceptor、MethodSecurityInterceptor 以及 AspectJSecurityInterceptor 都是它的具體實現類。
現在把門兒的倒是有了,可是他們不知道該攔誰,不該攔誰,所以,我們需要有類似神盾局(S.H.I.E.L.D)這樣的機構,由這個機構來決定誰可以放行,誰必須阻截,而在 SpringSecurity 框架中 AccessDecisionManager 就是這個控制機構,AccessDecisionManager 將決定誰可以訪問哪些資源。
現在剩下最後一個問題,這個誰怎麼定義?我們總得知道當前這個訪問者是誰才能告知 AccessDecisionManager 阻截還是放行,所以,SpringSecurity 框架中的 AuthenticationManager 將解決的是訪問者身份認證的問題,只有確定你在冊了,才可以給你授權訪問(除非匿名訪問某些公共資源)。


AuthenticationManager、AccessDecisionManager 和 AbstractSecurityInterceptor 屬於 Spring Security 框架的基礎鐵三角。AuthenticationManager 和 Access-DecisionManager 負責制定規則,AbstractSecurityInterceptor 負責執行。
所有針對不同應用場景的安全方案,基本上都是在這個基礎核心的基礎上衍生出來的,比如,Web 安全。
Spring Security 的 Web 安全方案基於 Java 的 Servlet API 規範進行構建,所以,像 Play Framework 這種脫離 Servlet 規範的 Web 框架,則無法享受到 SpringSecurity 提供的默認的 Web 安全方案(當然,依然可以基於基本模型實現擴展方案)。
既然是基於 Servlet API 規範,那麼,要實現關卡的“特效”,則非 javax.servlet.Filter 莫屬了。在使用 Spring 框架開發 Filter 的時候,為了讓 Filter 可以享受到依賴注入的好處,我們一般是實現 GenericFilterBean 並註冊到 IoC 容器。
為了能夠啟用這些註冊到 IoC 容器的 Filter,我們一般要在 web.xml 或者相應的 JavaConfig 的配置中聲明一個 org.springframework.web.filter.DelegatingFilterProxy,使其 filter-name 與 IoC 容器中我們希望啟用的 Filter 對應“掛鉤”,SpringSecurity 的 Web 安全方案的啟用也是這個原理。
SpringSecurity 默認會需要聲明一個默認名稱為“springSecurityFilterChain”的 org.springframework.web.filter.DelegatingFilterProxy(web.xml 方式或者 JavaConfig 方式),然後指向 IoC 容器中註冊的一個 org.springframework.security.web.FilterChainProxy 實例。
FilterChainProxy 通過擴展 GenericFilterBean 間接實現了 Filter 接口,同時持有一組 SecurityFilterChain,使它可以針對不同的 Web 資源進行特定的防護,這些“角兒”之間的關係大體上如圖 2 所示。

spring-boot-starter-security與應用安全

圖 2 FilterChainProxy 相關組件關係示意圖


當然,這些還只是“骨架”,真正執行防護任務的其實是一個個 org.springframework.security.web.SecurityFilterChain 中定義的一系列 Filter:

<code>public interface SecurityFilterChain {
boolean matches(HttpServletRequest request);
List<filter> getFilters();
}/<filter>/<code>

當我們經常看到如下的 xml schema 形式的配置格式的時候:

<code><http>
<intercept-url>
<intercept-url>
<form-login>
/<http>/<code>

其實一個個 http 元素背後對應的就是一個個 SecurityFilterChain 實例,而 http 元素的那些子元素,比如 intercept-url,則對應的就是一個個 Filter。
默認情況下,Spring Security 為 SecurityFilterChain 中的 Filter 序列設定了一個註冊框架,以 100 為間隔步長,按照一個合理的順序來規劃和排布常用的 Filter 實現(代碼參考FilterComparator):

<code>int order = 100;put(ChannelProcessingFilter.class, order);order += STEP;put(ConcurrentSessionFilter.class, order);order += STEP;put(WebAsyncManagerIntegrationFilter.class, order);order += STEP;put(SecurityContextPersistenceFilter.class, order);order += STEP;put(HeaderWriterFilter.class, order);order += STEP;put(CsrfFilter.class, order);order += STEP;put(LogoutFilter.class, order);order += STEP;put(X509AuthenticationFilter.class, order);order += STEP;put(AbstractPreAuthenticatedProcessingFilter.class, order);order += STEP;filterToOrder.put("org.springframework.security.cas.web.CasAuthenticationFilter", order);order += STEP;put(UsernamePasswordAuthenticationFilter.class, order);order += STEP;put(ConcurrentSessionFilter.class, order);order += STEP;filterToOrder.put("org.springframework.security.openid.OpenIDAuthenticationFilter", order);order += STEP;put(DefaultLoginPageGeneratingFilter.class, order);order += STEP;put(ConcurrentSessionFilter.class, order);order += STEP;put(DigestAuthenticationFilter.class, order);order += STEP;put(BasicAuthenticationFilter.class, order);order += STEP;put(RequestCacheAwareFilter.class, order);order += STEP;put(SecurityContextHolderAwareRequestFilter.class, order);order += STEP;put(JaasApiIntegrationFilter.class, order);order += STEP;put(RememberMeAuthenticationFilter.class, order);order += STEP;put(AnonymousAuthenticationFilter.class, order);order += STEP;put(SessionManagementFilter.class, order);order += STEP;put(ExceptionTranslationFilter.class, order);order += STEP;put(FilterSecurityInterceptor.class, order);order += STEP;put(SwitchUserFilter.class, order); /<code>

這些 Filter 雖然很多,但可以簡單劃分為幾類,除個別 Filter 在每個 SecurityFilterChain 都需要,其他可以根據需要選用並添加:

  1. 可以認為是信道與狀態管理,比如 ChannelProcessingFilter 用於處理 http 或者 https 之間的切換,而 SecurityContextPersistenceFilter 用於重建或者銷燬必要的 SecurityContext 狀態。
  2. 是常見 Web 安全防護類,比如 CsrfFilter。
  3. 是認證和授權,比如 BasicAuthenticationFilter、CasAuthen-ticationFilter 等。


最需要重點關注的是 FilterSecurityInterceptor,還記得我們前面說到的 secured object 吧。FilterSecurityInterceptor 就屬於放在 Web 訪問路徑上的那道“關卡”,現在,它的真實位置和效能終於浮出水面了。
ExceptionTranslationFilter 屬於另一個需要關注的核心類,它負責接待或者送客,如果訪客來訪,對方沒有報上名來,那麼,它會讓訪客去登記認證(去找 AuthenticationManager 做認證),如果對方報上名了,但認證失敗,那麼不好意思,請重新認證或者走人。當然,它拒絕訪客的方式是拋出相應的 Exception,所以名字叫 ExceptionTranslationFilter。
最後,這個 Filter 序列因為間隔了 100 的步長,所以,我們可以在其中穿插自己的 Filter 實現類,為定製和擴展 SpringSecurity 的防護體系提供了機會。
以上就是關於 Spring Security 以及其 Web 安全相關的基礎介紹,這些內容足以幫助我們理解並擴展 spring-boot-starter-security。

進一步定製spring-boot-starter-security

除了使用 SecurityProperties 暴露的配置項(以 security.* 開頭)對 spring-boot-starter-security 進行簡單的配置,我們還可以通過給出一個繼承了 WebSecurityConfigurerAdapter 的 JavaConfig 配置類對 spring-boot-starter-security 的行為進行更深一級的定製。
使用 WebSecurityConfigurerAdapter 的好處在於,我們依然可以使用 spring-boot-starter-security 默認約定的一些行為,只需要對必要的行為進行調整,比如:


  • 使用其他的 AuthenticationManager 實例。
  • 對默認 HttpSecurity 定義的資源訪問的規則進行重新定義。
  • 對默認提供的 WebSecurity 行為進行調整。


為了能夠讓這些調整生效,我們定義的 WebSecurityConfigurerAdapter 實現類一般在順序上需要先於 spring-boot-starter-security 默認提供的配置,故此,一般配合@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)進行標註,代碼如下所示:

<code>@Configuration@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)public class DemoSecurityConfiguration extends WebSecurity-ConfigurerAdapter {    protected DemoSecurityConfiguration() {        super(true); // 取消默認提供的安全相關Filters配置    }    @Override    public void configure(WebSecurity web) throws Exception {        // ...    }    @Override    protected void configure(HttpSecurity http) throws Exception {        // ...    }    // 通過Override其他方法實現對web安全的定製}/<code>

WebSecurityConfigurerAdapter 其實是為我們預先設定了一個框架,並開放了有限的一些擴展點允許我們對 Web 安全相關的設定進行定製,某些場景下還是會感覺“掣肘”,或者,某些有“潔癖”的開發者,往往不想使用在某些場景下顯得並非必要的默認設定。
這個時候,我們可以直接實現並註冊一個標註了 @EnableWebSecurity 的 JavaConfig 配置類到 IoC 容器,從而實現一種“顛覆性”的定製,即跟 spring-boot-starter-security 默認提供的 Web 安全相關配置一刀兩斷,完全自建:

<code>@Configuration@EnableWebSecuritypublic class OverhaulSecurityConfiguration {    @Bean    public AuthenticationManager authenticationManager() {        // ...    }    @Bean    public AccessDecisionManager accessDecisionManager() {        // ...    }    @Bean    public SecurityFilterChain mySecurityFilterChain() {        // ...    }    // 其他web安全相關組件和依賴配置}}/<code>

這種方案需要開發者對 Spring Security 框架本身以及 Web 安全本身有很深的理解,不到迫不得已,最好不要這麼做,威力大,風險也大。

最後

有需要Spring Boot視頻教程的小夥伴們注意啦:

點贊+關注+轉發+私信關鍵詞【boot】即可免費領取!!!


分享到:


相關文章: