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
應用安全通常可分紅兩個方面,一是認證:確認使用者的身份,建立對應的principal(這個詞表明一個通過確認的身份信息);二是鑑權:斷定某個principal是否有訪問某個資源或執行某個操做的權限。數據庫
Spring Security支持不少的認證方式好比HTTP BASIC, OPEN ID,FORM LOGI等等,這裏不列舉。而對於鑑權,支持3種主要類型:web請求,方法調用,以及domain對象。數組
因爲Spring Security支持的功能很普遍,這篇文章不會一一介紹。將背景限定爲:一個經過http協議訪問的web系統,採用表單登陸,用戶信息存儲在數據庫裏面。安全
這是使用Spring Security的必然要依賴的一個庫,其中包含了最基本的數據結構和接口。在Spring Security 3.0版本之後,這個庫通過簡化,再也不包含web、ldap、configuration相關的功能。從DDD的角度來看,這個庫是Spring Security的領域模型。下面介紹一下幾個最基本的類。restful
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(); }
大多數狀況下,principal是一個UserDetails實例。UserDetails是一個很重要的接口,表明認證用戶的詳細信息。咱們能夠經過自定義的類來實現它,或者使用security庫提供的簡單實現。無論如何,它是業務層用戶數據和spring security之間的橋樑,在必要時,咱們能夠把UserDetails轉換回具體類型,來訪問額外的字段。
建立UserDetails對象是一個叫作UserDetailsService的接口,它只有一個方法:
UserDetails loadUserByUsername(String username)
即經過用戶名字查詢UserDetails對象。無論實現如何,UserDetailsService被視做一個相似DAO的角色,參與到認證過程當中來。
除了principal,Authentication還包含一個GrantedAuthority數組。GrantedAuthority表明賦予principal的一項權限,最一般的狀況,是表明某個角色,好比「ROLE_ADMINISTRATOR」。
GrantedAuthority接口只有一個方法,就是String getAuthority()
,意味着若是你的鑑權機制經過字符串的處理就能完成,那麼經過字符串表達就好。前綴「ROLE_"就是一個約定,表明基於角色的權限。若是你的權限須要更復雜的數據結構來表示,那麼請自定義GrantedAuthority具體實現,這樣的話權限鑑定(後面會講)的過程也須要自定義。
一個簡化的Spring Security認證過程以下:
UsernamePasswordAuthenticationToken
(Authentication的實現);AuthenticationManager
;web應用的認證過程會稍微複雜一些,一樣通過簡化能夠表述以下:
鑑權決策的核心接口是AccessDecisionManager,它的核心方法是
void decide(Authentication authentication, Object object,Collection<ConfigAttribute> configAttributes)
第一個參數咱們已經知道是認證信息,第二個參數表明要訪問的受保護對象,第三個參數是這個資源的權限屬性集。
什麼是受保護對象?多是一個url請求,或者是某一個方法調用。不一樣類型的受保護資源,會有不一樣的攔截器(接口AbstractSecurityInterceptor)來攔截正常的訪問流程,插入權限決策機制。
攔截器完成如下工做:
權限屬性(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,表示明文存儲,不作任何處理。
這部分講一下Spring Security和Spring MVC的結合。上面講了Spring Security的核心配置,在web應用中,security的功能經過servlet filter的方式與web功能連接在一塊兒。這會使得整個配置更加複雜,所以Spring Security提供了簡潔的xml配置方式,一個簡單的
咱們都知道filter應該配置在web.xml中,實際上Spring Security只配置一個惟一的fiter,叫作DelegatingFilterProxy。它將實際的功能委託給其餘配置在Spring Context內的Spring Bean。
上面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的時候,可能會用到。
咱們基本不會相上面那樣配置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產生或大或小的影響,簡潔的同時也讓人難免暈頭轉向。
這是負責鑑權的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應當位於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配置了鑑權失敗的返回頁面。
這個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>
前面說了ExceptionTranslationFilter裏面有個authenticationEntryPoint,引導用戶去登陸。若是是採用用戶名和密碼登陸的方式,輸入的用戶名和密碼會被UsernamePasswordAuthenticationFilter接收,並完成認證。
<bean id="authenticationFilter" class= "org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter"> <property name="authenticationManager" ref="authenticationManager"/> </bean>
UsernamePasswordAuthenticationFilter裏面配置一個AuthenticationManager,關於AuthenticationManager的配置,前面已經講過。還能夠配置AuthenticationSuccessHandler,AuthenticationFailureHandler,對登陸成功或失敗後續行爲進行定製。
當第一個http元素出如今xml配置文件裏時,一個名叫springSecurityFilterChain
的FilterChainProxy會被建立出來,而且http的具體配置會用於建立一個完整的filter chain。繼續添加http元素,意味着建立額外的filter chain。 每一個http元素至少會建立SecurityContextPersistenceFilter, ExceptionTranslationFilter,FilterSecurityInterceptor這三個Filter,而且沒法替換成自定義版本(這裏指「沒法經過配置http元素的屬性和子元素來替換」)。
若是在配置文件裏面使用
http元素的重要屬性以下:
none
,表示對url pattern不使用安全策略;http元素的重要子元素以下:
具體xml配置請參考文檔,這裏對http元素作簡要說明,主要爲了闡述如何圍繞http這個元素,來構建一個完成的Filter chain。