Spring security深入雜談

spring security 是一個用於身份驗證和訪問控制的成熟框架,主要用於web的url訪問,當然她也可以用於更加細粒度的訪問控制(方法控制)但前者更具普遍意義,本文論述前者。
一 Quick start,一個簡單的例子

step1: 在web.xml中添加spring security的代理filter。

[html] view plaincopy
  1. <filter>  
  2.     <filter-name>springSecurityFilterChain</filter-name>  
  3.     <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>  
  4. </filter>  
  5. <filter-mapping>  
  6.     <filter-name>springSecurityFilterChain</filter-name>  
  7.     <url-pattern>/*</url-pattern>  
  8. </filter-mapping>  

step2: spring-security-context.xml
[html] view plaincopy
  1. <beans:beans xmlns="http://www.springframework.org/schema/security"  
  2.     xmlns:beans="http://www.springframework.org/schema/beans"  
  3.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  4.     xsi:schemaLocation="  
  5.       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
  6.       http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">  
  7.   
  8.     <http>  
  9.         <intercept-url pattern="/secure/extreme/**" access="ROLE_SUPERVISOR" />  
  10.         <intercept-url pattern="/secure/**" access="IS_AUTHENTICATED_FULLY" />  
  11.         <intercept-url pattern="/login.htm" access="IS_AUTHENTICATED_ANONYMOUSLY" />  
  12.         <intercept-url pattern="/images/*" filters="none" />  
  13.         <intercept-url pattern="/**" access="ROLE_USER" />  
  14.         <form-login login-page="/login.htm" default-target-url="/home.htm" />  
  15.         <logout logout-success-url="/logged_out.htm" />  
  16.     </http>  
  17.   
  18.     <authentication-manager>  
  19.         <authentication-provider>  
  20.             <password-encoder hash="md5"/>  
  21.             <user-service>  
  22.                 <user name="bob" password="12b141f35d58b8b3a46eea65e6ac179e" authorities="ROLE_SUPERVISOR, ROLE_USER" />  
  23.                 <user name="sam" password="d1a5e26d0558c455d386085fad77d427" authorities="ROLE_USER" />  
  24.             </user-service>  
  25.         </authentication-provider>  
  26.     </authentication-manager>  
  27.   
  28. </beans:beans>  
好了,這是一個來自spring官網的例子,我們居然沒寫代碼!好吧,我們看看這些配置都幹了些什麼.
首先是在web.xml中定義了一個名爲:“springSecurityFilterChain”的過濾器。然後就是在spring的上下文文件中:
    <intercept-url pattern="/**" access="ROLE_USER" />
表示訪問任何url的用戶都必須擁有"ROLE_USER"的權限,而用戶權限的定義是在
    <user name="bob" password="12b141f35d58b8b3a46eea65e6ac179e" authorities="ROLE_SUPERVISOR, ROLE_USER" />定義的。
仔細看看這些定義,你是不是覺得有點迷惑:
    <intercept-url pattern="/images/*" filters="none" />
表明對"/images/*"的訪問是不需要任何權限的爲什麼"/**"又需要"ROLE_USER"的權限呢,對了,順序很重要,url的權限判斷是根據從上到下的順序匹配第一個的。
    <form-login login-page="/login.htm" default-target-url="/home.htm" />
定義了登錄頁面“login-page”, 並且登錄成功展示的頁面“default-target-url”, 等等。
到這兒其實就是一個完整的spring security 的配置了,已經能夠work了,是不是毫無成就感,這一切都是怎麼發生的呢,一點都沒展示!

好吧,我們開始抽絲剝繭,一點一點的講明白這件事情。


二 這一切都是怎麼發生的。
1. Web.xml中的filter

[html] view plaincopy
  1. <filter>  
  2.     <filter-name>springSecurityFilterChain</filter-name>  
  3.     <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>  
  4. </filter>  
  5. <filter-mapping>  
  6.     <filter-name>springSecurityFilterChain</filter-name>  
  7.     <url-pattern>/*</url-pattern>  
  8. </filter-mapping>  
spring security 對web的保護是通過過濾器完成的,沒錯,就是你想到的那個“javax.servlet.Filter”。
這個名爲“springSecurityFilterChain”    的filter會攔截所有的訪問請求進行權限校驗和訪問控制。
“springSecurityFilterChain”是個代理,它將攔截到的請求委託給它代理的若干個filter進行校驗。這些過濾器按照一定的順序組成一個鏈對請求進行處理,這也體現在
代理的名字中“Chain”。這些filter的順序和數量都是可配置的(在3.0中不可配置了,但是仍舊可以添加新的filter到任何位置),對這種需求最好的解決方法就是依賴注入(IOC)可惜在web.xml中時沒有IOC的,這就是爲什麼是個代理的原因:爲了獲取IOC的支持,所以真正的配置是在spring的上下文中。
其實org.springframework.web.filter.DelegatingFilterProxy,這個類所代理的類是名爲springSecurityFilterChain的類,是在springcontext中定義的,在spring security3.0中這個類被框架自動創建了,是個org.springframework.security.web.FilterChainProxy的instance。這一點在後面會另外講。

2. spring-security-contect.xml
[html] view plaincopy
  1. <beans:beans xmlns="http://www.springframework.org/schema/security"  
  2.     xmlns:beans="http://www.springframework.org/schema/beans"  
  3.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  4.     xsi:schemaLocation="  
  5.       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
  6.       http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">  
  7.   
  8.     <http>  
  9.         <intercept-url pattern="/secure/extreme/**" access="ROLE_SUPERVISOR" />  
  10.         <intercept-url pattern="/secure/**" access="IS_AUTHENTICATED_FULLY" />  
  11.         <intercept-url pattern="/login.htm" access="IS_AUTHENTICATED_ANONYMOUSLY" />  
  12.         <intercept-url pattern="/images/*" filters="none" />  
  13.         <intercept-url pattern="/**" access="ROLE_USER" />  
  14.         <form-login login-page="/login.htm" default-target-url="/home.htm" />  
  15.         <logout logout-success-url="/logged_out.htm" />  
  16.     </http>  
  17.   
  18.     <authentication-manager>  
  19.         <authentication-provider>  
  20.             <password-encoder hash="md5"/>  
  21.             <user-service>  
  22.                 <user name="bob" password="12b141f35d58b8b3a46eea65e6ac179e" authorities="ROLE_SUPERVISOR, ROLE_USER" />  
  23.                 <user name="sam" password="d1a5e26d0558c455d386085fad77d427" authorities="ROLE_USER" />  
  24.             </user-service>  
  25.         </authentication-provider>  
  26.     </authentication-manager>  
配置很簡單,因爲在spring security3.0中引入了命名空間,也就是說使用了命名空間框架就會幫你做大量的默認工作,好處就是配置簡單,壞處就是細節
被掩蓋了,你不明白爲什麼。呵呵。
按照之前的思路,在spring的上下文中應該有叫做:“springSecurityFilterChain”的類,它定義了很多的filter來處理請求,你想的沒錯,只是這個細節被
<http>這個標籤掩蓋了。
<http>的重要使命?
創建了filter鏈(被掩蓋的細節終於出現了)
[html] view plaincopy
  1. <alias name="filterChainProxy" alias="springSecurityFilterChain"/>  
  2.   
  3. <bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy">  
  4.     <sec:filter-chain-map path-type="ant">  
  5.         <sec:filter-chain pattern="/images/*" filters="none"/>  
  6.         <sec:filter-chain pattern="/**" filters="securityContextFilter, logoutFilter, formLoginFilter, requestCacheFilter,servletApiFilter, anonFilter, sessionMgmtFilter, exceptionTranslator, filterSecurityInterceptor" />  
  7.     </sec:filter-chain-map>  
  8. </bean>  
<http>首先建立了"filterChainProxy"的bean並且爲每一個url配上了默認的filter鏈,然後將"filterChainProxy"起了個"springSecurityFilterChain"的別名,很眼熟吧,就是在web.xml中配置的那個filter名。有一個來自spring官方網站的圖可以清楚的看到<http>都內置了哪些filter:這些filter有的是可選的,有的是必須的。

這些filter被<http>事先生成,並且順序也是確定的。spring security不允許覆蓋或者改變已經內置的filter,所以如果你想添加一些更加特殊化的處理只能在某個位置追加自己定義的filter,這一點spring security是支持的。
在spring security中對web資源的保護共有17種過濾器,大部分一般都用不上,必須的有四種:
    1.HttpSessionIntegrationFilter: 記住用戶信息。
    2.authenticationProcessingFilter: 配置用戶登錄頁面,提示用戶登錄,並且對用戶的身份進行驗證,在該filter中有一個屬性叫做                authenticationEntryPoint,這個bean中可以配置登錄頁面。
    3.exceptionTranslationFilter: 更友善的對用戶展示異常。
    4.filterSecurityInterceptor: 根據用戶所擁有的權限和資源所要求的權限進行最終的判定。
好了,到這裏filter這件事情基本上講完了,這些filter都幹了些什麼呢?

3. filter中都做了什麼
在這個框架中,主要做了兩件事情:用戶驗證和訪問控制,需要做這兩件事情的filter會注入authenticationManager(用戶驗證)和accessDecsionManager(訪問控制),先說authenticationManager,它負責用戶驗證,但是它本身不幹活,把活派給了authenticationProvider,spring security是個貼心的框架,本身提供了大量的provider供用戶使用,如數據庫認證,JAAS認證等等,
如果還沒有適合的隨時可以實現authentication provider去創建自己的provider。其實provider本身也不幹活,它把活派發給了另外一個苦逼接口,UserDetailService,
它是真正的幹活的,它有一個方法:public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException, DataAccessException{}
用來獲取用戶信息,如果信息存在則返回一個UserDetail對象,如果不存在則拋出UsernameNotFoundException。這個方法是需要用戶自己實現的。如果驗證成功,
provider 會生成一個Authentication對象並將這個對象放入org.springframework.security.core.context.SecurityContext這個當前應用的安全上下文中,這樣的話當前應用在任何時候都可以獲判斷當前用戶是否已經通過認證,以及獲取當前用戶的有關信息,如用戶名,密碼,權限等。
再說說accessDecsionManager,它負責判斷當前用戶是否有權限訪問某個資源,它也不幹活,派給了若干個AccessDecisionVoter, 這些個接口負責投票,會有三種票:贊成,反對,棄權,而accessDecsionManager有三種不同的實現,分別實現了三種策略:只要有一個贊成票就允許訪問;只要大多數贊成就允許訪問;都是贊成票才允許訪問。這樣就決定了一個用戶是否有權訪問一個資源。一般來說spring security提供了一個默認的實現:org.springframework.security.vote.RoleVoter就足以滿足大多數的需求了,這個類只要被訪問的資源需要role_作爲前綴的權限它就會通過比較user擁有的權限和資源需要的權限來進行投票,否則就投棄權票。知道了這個特性就可以針對它來設置自己的用戶權限和資源權限(使用role_作爲前綴),一般沒有必要自己實現。當然這個“role_”前綴的規則是可以通過設置來改變的。

講到這基本上都差不多了,說一個工作中的bug吧:
用戶反映登錄頁面在出現異常情況時,如數據庫down掉或者網絡不通時總是報錯:“該用戶沒有權限”,這個確實不妥。經過查找在UserDetailService的實現類中方法loadUserByUsername中無論發生了任何異常都會catch而後拋出UsernameNotFoundException,而在filter:AuthenticationProcessingFilter中如果一旦認證失敗,它會獲取這個失敗的異常並將這個exception寫入session中,key值爲:“SPRING_SECURITY_LAST_EXCEPTION”,這樣在jsp中就可以從session中獲取這個exception,判斷後告訴用戶到底是什麼錯誤。無論什麼錯誤只拋UsernameNotFoundException就導致jsp只看到這個異常也就只好老是報錯“該用戶沒有權限”了。改正的方法很簡單,實現若干個繼承了org.springframework.security.core.AuthenticationException(UsernameNotFoundException的父類)的異常,在catch到不同的錯誤時拋出然後在jsp中進行判斷就可以了。

講到這基本上都差不多了,最後再說一點,spring security提供了一個JSP標籤庫來方便在JSP頁面中最安全信息進行獲取。感興趣可以自己看看,這裏不詳細說明了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章