Spring Security核心概念介紹

Spring Security是一個強大的java應用安全管理庫,特別適合用做後臺管理系統。這個庫涉及的模塊和概念有必定的複雜度,而你們平時學習Spring的時候也不會涉及;這裏基於官方的參考文檔,把Spring Security的基本套路介紹一下。html

參考的Spring Security文檔地址:https://docs.spring.io/spring-security/site/docs/5.0.7.RELEASE/reference/html/preface.htmljava

Spring Securitys示例https://docs.spring.io/spring-security/site/docs/5.0.7.RELEASE/reference/html/samples.html;對於新手入門,看一下示例頗有必要,可是通常的產品的安全的策略都比示例要複雜得多,很難經過模仿示例程序來達成你的目標。web

說明:這篇文章不打算手把手教你們如何使用Spring Security,因此不會有詳細的代碼以及配置;少許的代碼和配置示例,僅僅用來闡述概念和設計,這些示例代碼和配置並不必定適合在項目中使用。spring

Over View

認證和鑑權("authentication" and "authorization" )

應用安全通常可分紅兩個方面,一是認證:確認使用者的身份,建立對應的principal(這個詞表明一個通過確認的身份信息);二是鑑權:斷定某個principal是否有訪問某個資源或執行某個操做的權限。數據庫

Spring Security支持不少的認證方式好比HTTP BASIC, OPEN ID,FORM LOGI等等,這裏不列舉。而對於鑑權,支持3種主要類型:web請求,方法調用,以及domain對象。數組

因爲Spring Security支持的功能很普遍,這篇文章不會一一介紹。將背景限定爲:一個經過http協議訪問的web系統,採用表單登陸,用戶信息存儲在數據庫裏面。安全

Security-Core

這是使用Spring Security的必然要依賴的一個庫,其中包含了最基本的數據結構和接口。在Spring Security 3.0版本之後,這個庫通過簡化,再也不包含web、ldap、configuration相關的功能。從DDD的角度來看,這個庫是Spring Security的領域模型。下面介紹一下幾個最基本的類。restful

SecurityContextHolder

SecurityContextHolder是存放當前安全相關上下文對象的地方,它包含一個Authentication對象,包含認證用戶的多有信息。它使用ThreadLocal來存放信息,請求執行結束之後清除相關信息。所以若是你須要在其餘線程訪問Security上下文信息,請注意這一點。session

下面的代碼展現瞭如何經過contextHolder訪問principal。數據結構

Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();

if (principal instanceof UserDetails) {
    String username = ((UserDetails)principal).getUsername();
} else {
    String username = principal.toString();
}

UserDetails

大多數狀況下,principal是一個UserDetails實例。UserDetails是一個很重要的接口,表明認證用戶的詳細信息。咱們能夠經過自定義的類來實現它,或者使用security庫提供的簡單實現。無論如何,它是業務層用戶數據和spring security之間的橋樑,在必要時,咱們能夠把UserDetails轉換回具體類型,來訪問額外的字段。

建立UserDetails對象是一個叫作UserDetailsService的接口,它只有一個方法:

UserDetails loadUserByUsername(String username)

即經過用戶名字查詢UserDetails對象。無論實現如何,UserDetailsService被視做一個相似DAO的角色,參與到認證過程當中來。

GrantedAuthority

除了principal,Authentication還包含一個GrantedAuthority數組。GrantedAuthority表明賦予principal的一項權限,最一般的狀況,是表明某個角色,好比「ROLE_ADMINISTRATOR」。

GrantedAuthority接口只有一個方法,就是String getAuthority(),意味着若是你的鑑權機制經過字符串的處理就能完成,那麼經過字符串表達就好。前綴「ROLE_"就是一個約定,表明基於角色的權限。若是你的權限須要更復雜的數據結構來表示,那麼請自定義GrantedAuthority具體實現,這樣的話權限鑑定(後面會講)的過程也須要自定義。

認證

一個簡化的Spring Security認證過程以下:

  1. 用戶輸入用戶名和密碼;被包裝成UsernamePasswordAuthenticationToken(Authentication的實現);
  2. 這個token傳遞到AuthenticationManager
  3. AuthenticationManager驗證後,返回一個徹底填充(fully populated)的Authentication對象;
  4. 經過SecurityContextHolder.getContext().setAuthentication,完成安全上下文的建立。

web應用的認證過程會稍微複雜一些,一樣通過簡化能夠表述以下:

  1. 用戶訪問某個受保護的url
  2. AbstractSecurityInterceptor攔截這個請求,並拋出沒有權限
  3. ExceptionTranslationFilter捕獲這個異常
  4. 若是檢測到用戶沒有認證,因而經過AuthenticationEntryPoint重定向用戶到一個登陸頁面;
  5. 若是發現已經認證可是權限不足,一般返回HTTP 403.
  6. 接下來的認證過程和上面是相似的。

鑑權機制

鑑權決策的核心接口是AccessDecisionManager,它的核心方法是

void decide(Authentication authentication, Object object,Collection<ConfigAttribute> configAttributes)

第一個參數咱們已經知道是認證信息,第二個參數表明要訪問的受保護對象,第三個參數是這個資源的權限屬性集。

什麼是受保護對象?多是一個url請求,或者是某一個方法調用。不一樣類型的受保護資源,會有不一樣的攔截器(接口AbstractSecurityInterceptor)來攔截正常的訪問流程,插入權限決策機制。

攔截器完成如下工做:

  1. 查找當前受保護對象的權限屬性;
  2. 將受保護對象(secure object),權限屬性(configuration attributes),當前的認證信息(authentication),提交給AccessDecisionManager來鑑權;
  3. 若是鑑權經過,繼續執行正常的訪問;
  4. 不然拋出異常;

權限屬性(configuration attribute)是受保護對象的,與權限相關的屬性數據,通常就是普通的字符串。對這個屬性的解釋取決於AccessDecisionManager的實現。經過爲AbstractSecurityInterceptor配置SecurityMetadataSource來實現權限屬性的查找。好比,在xml配置裏面看到<intercept-url pattern='/secure/**' access='ROLE_A,ROLE_B'/>,那麼配置屬性「ROLE_A」和「ROLE_B」暗示角色A和B能訪問這個pattern的url;固然實際是否如此還要看AccessDecisionManager的配置。這裏再強調一下,"ROLE_"這個前綴是Spring Security內的一種約定,用於基於角色的鑑權機制。

核心的服務

AuthenticationManager僅僅是一個接口,具體的實現取決於認證的方式。Spring Security的默認實現叫作ProviderManager,它把認證功能委託給一個AuthenticationProvider列表。每一個AuthenticationProvider能夠返回一個徹底填充(fully populated)的Authentication對象(認證成功),或拋出一個異常;可見,ProviderManager能夠組合多種認證方式,一個ProviderManager bean的配置相似以下:

<bean id="authenticationManager"
        class="org.springframework.security.authentication.ProviderManager">
    <constructor-arg>
        <list>
            <ref local="daoAuthenticationProvider"/>
            <ref local="anonymousAuthenticationProvider"/>
            <ref local="ldapAuthenticationProvider"/>
        </list>
    </constructor-arg>
</bean>

上一章節講到UserDetailsService能夠經過用戶名加載用戶的信息(UserDetails),實現該種認證方式的Manager是DaoAuthenticationProvider,他內部配置一個UserDetailsService引用:

<bean id="daoAuthenticationProvider"
    class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="inMemoryDaoImpl"/>
<property name="passwordEncoder" ref="passwordEncoder"/>
</bean>

上面的PasswordEncoder用於用戶密碼的編解碼。通常用戶的密碼不會以明文形式存儲,同時加密方式也不會支持逆向解密;PasswordEncoder能夠將輸入的密碼進行加密,再與存儲的密文進行比較。爲了支持同時多種加密方式,Spring Security設計了叫作DelegatingPasswordEncoder的encoder,他將加密委託給多種具體加密方式,依據密文類型查詢。因而默認的密文存儲格式就變成了{id}encodedPassword。一個特殊的encode是NoOpPasswordEncoder,表示明文存儲,不作任何處理。

Web應用安全

這部分講一下Spring Security和Spring MVC的結合。上面講了Spring Security的核心配置,在web應用中,security的功能經過servlet filter的方式與web功能連接在一塊兒。這會使得整個配置更加複雜,所以Spring Security提供了簡潔的xml配置方式,一個簡單的 標籤後面,完成了大量功能。

DelegatingFilterProxy

咱們都知道filter應該配置在web.xml中,實際上Spring Security只配置一個惟一的fiter,叫作DelegatingFilterProxy。它將實際的功能委託給其餘配置在Spring Context內的Spring Bean。

FilterChainProxy

上面DelegatingFilterProxy將功能委託給FilterChainProxy,FilterChainProxy是一個普通的Spring bean,若是要在xml裏面聲明它,注意bean id應當於web.xml裏面聲明DelegatingFilterProxy的filter-name是同樣的。

FilterChainProxy的名字暗示了,它背後有不少filter組成的chain,實際上它能夠包含多個chain,下面看示例:

<bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy">
<constructor-arg>
    <list>
    <sec:filter-chain pattern="/restful/**" filters="
        securityContextPersistenceFilterWithASCFalse,
        basicAuthenticationFilter,
        exceptionTranslationFilter,
        filterSecurityInterceptor" />
    <sec:filter-chain pattern="/**" filters="
        securityContextPersistenceFilterWithASCTrue,
        formLoginFilter,
        exceptionTranslationFilter,
        filterSecurityInterceptor" />
    </list>
</constructor-arg>
</bean>

上面對不一樣的url路徑使用了不一樣的安全策略,而不一樣的安全策略是經過一個filter chain來實現的。這些filter的所有實現java.servlet.filter接口,但並不禁web容器來管理。前面所說的那些「認證」,「鑑權」相關功能實現都隱藏在這些filter背後。

這些Filter有嚴格的順序,好比受權相關的Filter就要出如今鑑權相關的Fiter以前,關於位置,Spring Security定義了一組常量。當你想提供一個自定義的Filter的時候,可能會用到。

xml配置之http(示例)

咱們基本不會相上面那樣配置FilterChain,在xml文件裏面一個http元素,就意味着一條完整的FilterChain被配置,Spring Security的配置模塊爲咱們完成大量的工做。

<!-- Stateless RESTful service using Basic authentication -->
<http pattern="/restful/**" create-session="stateless">
<intercept-url pattern='/**' access="hasRole('REMOTE')" />
<http-basic />
</http>

<!-- Empty filter chain for the login page -->
<http pattern="/login.htm*" security="none"/>

<!-- Additional filter chain for normal users, matching all other requests -->
<http>
<intercept-url pattern='/**' access="hasRole('USER')" />
<form-login login-page='/login.htm' default-target-url="/home.htm"/>
<logout />
</http>

上面三個http元素,第一個對/restful/**使用基於http-basic的登陸方式,使用基於角色的鑑權方式(角色REMOTE能夠訪問)。
第二個對/login.htm*不使用任何安全過濾;和第一個相比,使用form-login的登陸方式,並指定了登陸url和登陸後跳轉的url,還配置了登出時的默認行爲。

http元素的每一個屬性,每一個子元素,均可能對filter chain產生或大或小的影響,簡潔的同時也讓人難免暈頭轉向。

核心安全過濾器

FilterSecurityInterceptor

這是負責鑑權的Filter,典型的配置以下:

<bean id="filterSecurityInterceptor"
    class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="accessDecisionManager"/>
<property name="securityMetadataSource">
    <security:filter-security-metadata-source>
    <security:intercept-url pattern="/secure/super/**" access="ROLE_WE_DONT_HAVE"/>
    <security:intercept-url pattern="/secure/**" access="ROLE_SUPERVISOR,ROLE_TELLER"/>
    </security:filter-security-metadata-source>
</property>
</bean>

前面的章節講過,鑑權的過程就是將Authentication,安全對象,安全對象的配置屬性,提交給AccessDecisionManager。上面的配置可見,這個Filter包含AuthenticationManager,AccessDecisionManager引用,而內嵌的securityMetadataSource提供了安全對象的配置屬性。

ExceptionTranslationFilter

ExceptionTranslationFilter應當位於FilterSecurityInterceptor以前,它起一個粘合劑的做用。負責捕獲權限相關的異常,若是用戶當前沒有登陸,則引導應用去登陸界面,不然返回失敗結果:

<bean id="exceptionTranslationFilter"
class="org.springframework.security.web.access.ExceptionTranslationFilter">
<property name="authenticationEntryPoint" ref="authenticationEntryPoint"/>
<property name="accessDeniedHandler" ref="accessDeniedHandler"/>
</bean>

<bean id="authenticationEntryPoint"
class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<property name="loginFormUrl" value="/login.jsp"/>
</bean>

<bean id="accessDeniedHandler"
    class="org.springframework.security.web.access.AccessDeniedHandlerImpl">
<property name="errorPage" value="/accessDenied.htm"/>
</bean>

這個配置展現了ExceptionTranslationFilter的典型功能,authenticationEntryPoint定義了登陸入口,accessDeniedHandler配置了鑑權失敗的返回頁面。

SecurityContextPersistenceFilter

這個Filter用來在request之間保存Security Context;它的默認配置以下,使用HttpSession來保存conext。

<bean id="securityContextPersistenceFilter"
    class="org.springframework.security.web.context.SecurityContextPersistenceFilter">
<property name='securityContextRepository'>
    <bean class='org.springframework.security.web.context.HttpSessionSecurityContextRepository'>
    <property name='allowSessionCreation' value='true' />
    </bean>
</property>
</bean>

UsernamePasswordAuthenticationFilter

前面說了ExceptionTranslationFilter裏面有個authenticationEntryPoint,引導用戶去登陸。若是是採用用戶名和密碼登陸的方式,輸入的用戶名和密碼會被UsernamePasswordAuthenticationFilter接收,並完成認證。

<bean id="authenticationFilter" class=
"org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
<property name="authenticationManager" ref="authenticationManager"/>
</bean>

UsernamePasswordAuthenticationFilter裏面配置一個AuthenticationManager,關於AuthenticationManager的配置,前面已經講過。還能夠配置AuthenticationSuccessHandler,AuthenticationFailureHandler,對登陸成功或失敗後續行爲進行定製。

xml配置之http(解釋)

當第一個http元素出如今xml配置文件裏時,一個名叫springSecurityFilterChain的FilterChainProxy會被建立出來,而且http的具體配置會用於建立一個完整的filter chain。繼續添加http元素,意味着建立額外的filter chain。 每一個http元素至少會建立SecurityContextPersistenceFilter, ExceptionTranslationFilter,FilterSecurityInterceptor這三個Filter,而且沒法替換成自定義版本(這裏指「沒法經過配置http元素的屬性和子元素來替換」)。

若是在配置文件裏面使用 定義了AuthenticationManager,那麼http動建立的全部Filter,都會按需自動注入這個manager。

http元素的重要屬性以下:

  • access-decision-manager-ref 指向AccessDecisionManager,後者經過Spring bean來定義;
  • authentication-manager-ref 指向AuthenticationManager,後者經過Spring bean來定義;
  • entry-point-ref,指向AuthenticationEntryPoint,後者經過Spring bean來定義;
  • pattern,指定urli匹配模式
  • security,若是要設置的話,只能是none,表示對url pattern不使用安全策略;

http元素的重要子元素以下:

  • access-denied-handler,鑑權失敗的處理器;
  • form-login,會建立UsernamePasswordAuthenticationFilter,以及LoginUrlAuthenticationEntryPoint;
  • logout, 建立LogoutFilter,後者和SecurityContextLogoutHandler一塊兒工做;
  • session-management,建立SessionManagementFilter;
  • custom-filter, 添加自定義的Fiter,後者經過spring bean定義;經過屬性,可指定該filter放在某個標準filter的前面,後面,或取代這個標準filter。

具體xml配置請參考文檔,這裏對http元素作簡要說明,主要爲了闡述如何圍繞http這個元素,來構建一個完成的Filter chain。

相關文章
相關標籤/搜索