spring 2.5也發布了,Acegi 2.0也出來了,發現里面也多了很多新特性,不過好多都是英文的,所以就到處看看,記些東西,謂之筆記也,呵呵。廢話不多說,配置文件當然要從web.xml開始啊。看代碼。
使用安全框架第一步就是需要在web.xml文件中聲明要使用的過濾器<filter></filter>
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
以上代碼在Spring Securtity網絡構建下定義了一個鉤子,接著你就可以開始編輯你的應用程序配置文件(這個就不是Web.xml了)。網頁安全服務使用<http>元素配置。
給你們看個最小應用的<http>設置
<http auto-config='true'>
<intercept-url pattern="/**" access="ROLE_USER" />
</http>
以上代碼說明希望我們應用程序的所有URLS都被保護起來,并要求由角色為ROLE_USER登入(注意:你可以使用若干的<intercept-url>元素為不同的URLS設置不同的用戶角色登錄請求,但是它們會在合適的排列中進行評估,而且第一個匹配的將被使用。所以你應該將最細節的配置部分放在最前面!)
為了加一些用戶,你可以在命名空間里面定義一些測試數據:
<authentication-provider>
<user-service>
<user name="jimi" password="jimispassword" authorities="ROLE_USER, ROLE_ADMIN" />
<user name="bob" password="bobspassword" authorities="ROLE_USER" />
</user-service>
</authentication-provider>
注意:<http>元素是為創建一個FilterChainProxy和它要使用的若干filter beans,因為filter順序不正確產生的問題,不會再出現了,現在這些過濾器的位置都是預定義好的。
<authentication-provider>元素創建一個DaoAuthenticationProvider bean,<user-service>元素創建一個InMemoryDaoImpl.一個ProviderManageer bean 總是由命名空間處理系統所創建而且DaoAuthenticationProvider自動被ProviderManageer bean注冊。
我們接著說測試用戶,上面定義了2個用戶,他們的密碼和角色被應用于程序中的登錄控制。它也可以從<user-service>的配置屬性中載入來自標準的屬性文件中的用戶信息。使用<authentication-provider>元素意味著用戶信息將被authentication manager用來處理認證請求。
接下來說下auto-config屬性,就如上面的配置定義一樣,像這樣定義就可以了
<http>
<intercept-url pattern="/**" access="ROLE_USER" />
<form-login />
<anonymous />
<http-basic />
<logout />
<remember-me />
</http>
以上這些元素負責啟用用戶登錄、匿名認證、基礎認證、注銷和remember-me服務,他們都有各自的屬性去改變他們的行為。auto-config需要一個UserDetailsService.在使用沒有UserDetailsService的auto-config時在你的配置文件中會發成一個錯誤(比如你正在使用LDAP認證)。這是因為remember-me在auto-config="true"的時候會自動激活而且它還要求一個使用UserDetailsService運行的認證機制,那么如果你有一個因缺少UserDetailsService而產生的錯誤,請嘗試移除auto-config的設置。
好了我們接下來談下表單和基本登錄選項,你可能會奇怪當提示你要登錄的時候登錄表單是從哪兒來的,因為我們并沒有提供任何HTML文件或者JSP文件。事實上,我們并沒有為登錄頁面明確的設置URL。 Spring Security 會產生一個自動的、基于自動激活并使用專為URL提交登錄的標準參數特性,對于默認目標URL,用戶會自動發送相應信息。然而,命名空間提供足夠豐富的支持允許你自定義這些選項。比如,你想提供自己的登錄頁面,你可以這么做:
<http auto-config='true'>
<intercept-url pattern="/login.jsp*" filters="none"/>
<intercept-url pattern="/**" access="ROLE_USER" />
<form-login login-page='/login.jsp'/>
</http>
你依然能用auto-config,form-login元素僅僅是重寫了默認的設置,同樣的我們已經添加了額外的intercept-url元素告訴登錄頁面所有的請求都應該被安全過濾器所處理,另外,請求也應該被模式/**進行匹配并且它不能轉向它自己的登錄頁面,如果你想使用basic authentication來代替form login,那么請修改配置文件如下:
<http auto-config='true'>
<intercept-url pattern="/**" access="ROLE_USER" />
<http-basic />
</http>
Basic authentication將會獲得優先并將用于登錄提示當一個用戶試圖訪問被保護的資源。如果你希望使用它的話Form login依然可以在這份配置文件中使用。
現實中,你會需要更大型的用戶信息源,而不是寫在application context里的幾個名字。 多數情況下,你會想把用戶信息保存到數據庫或者是LDAP服務器里。 LDAP命名控件會在LDAP章里詳細討論,所以我們這里不會講它。 如果你自定義了一個Spring Security的UserDetailsService實現,在你的application context中名叫"myUserDetailsService",然后你可以使用下面的驗證。
<authentication-provider user-service-ref='myUserDetailsService'/>
如果你想用數據庫,可以使用下面的方式
<authentication-provider>
<jdbc-user-service data-source-ref="securityDataSource"/>
</authentication-provider>
這里的"securityDataSource"就是 DataSource bean在application context里的名字,它指向了包含著Spring Security用戶信息的表。 另外,你可以配置一個Spring Security JdbcDaoImpl bean,使用user-service-ref屬性指定。
2.2.3.1. 添加一個密碼編碼器
你的密碼數據通常要使用一種散列算法進行編碼。 使用<password-encoder>元素支持這個功能。 使用SHA加密密碼,原始的認證供應器配置,看起來就像這樣:
<authentication-provider>
<password-encoder hash="sha"/>
<user-service>
<user name="jimi" password="d7e6351eaa13189a5a3641bab846c8e8c69ba39f" authorities="ROLE_USER, ROLE_ADMIN" />
<user name="bob" password="4e7421b1b8765d8f9406d87e7cc6aa784c4ab97f" authorities="ROLE_USER" />
</user-service>
</authentication-provider>
在使用散列密碼時,用鹽值防止字典攻擊是個好主意,Spring Security也支持這個功能。 理想情況下,你可能想為每個用戶隨機生成一個鹽值,不過,你可以使用從UserDetailsService讀取出來的UserDetails對象中的屬性。 比如,使用username屬性,你可以這樣用:
<password-encoder hash="sha">
<salt-source user-property="username"/>
</password-encoder>
你可以通過password-encoder的ref屬性,指定一個自定義的密碼編碼器bean。 這應該包含application context中一個bean的名字,它應該是Spring Security的PasswordEncoder接口的一個實例。
2.3. 高級web特性
2.3.1. Remember-Me認證
參考Remember-Me章獲得remember-me命名空間配置的詳細信息。
2.3.2. 添加HTTP/HTTPS信道安全
如果你的同時支持HTTP和HTTPS協議,然后你要求特定的URL只能使用HTTPS,這時可以直接使用<intercept-url>的requires-channel屬性:
<http>
<intercept-url pattern="/secure/**" access="ROLE_USER" requires-channel="https"/>
<intercept-url pattern="/**" access="ROLE_USER" requires-channel="any"/>
...
</http>
使用了這個配置以后,如果用戶通過HTTP嘗試訪問"/secure/**"匹配的網址,他們會先被重定向到HTTPS網址下。 可用的選項有"http", "https" 或 "any"。 使用"any"意味著使用HTTP或HTTPS都可以。
如果你的程序使用的不是HTTP或HTTPS的標準端口,你可以用下面的方式指定端口對應關系:
<http>
...
<port-mappings>
<port-mapping http="9080" https="9443"/>
</port-mappings>
</http>
你可以在Chapter 7, Channel Security找到更詳細的討論。
2.3.3. 同步Session控制
如果你希望限制單個用戶只能登錄到你的程序一次,Spring Security通過添加下面簡單的部分支持這個功能。 首先,你需要把下面的監聽器添加到你的web.xml文件里,讓Spring Security獲得session生存周期事件:
<listener>
<listener-class>org.springframework.security.ui.session.HttpSessionEventPublisher</listener-class>
</listener>
然后,在你的application context加入如下部分:
<http>
...
<concurrent-session-control max-sessions="1" />
</http>
這將防止一個用戶重復登錄好幾次-第二次登錄會讓第一次登錄失效。 通常我們更想防止第二次登錄,這時候我們可以使用
<http>
...
<concurrent-session-control max-sessions="1" exception-if-maximum-exceeded="true"/>
</http>
第二次登錄將被阻止。
2.3.4. OpenID登錄
命名空間支持OpenID登錄,替代普通的表單登錄,或作為一種附加功能,只需要進行簡單的修改:
<http auto-config='true'>
<intercept-url pattern="/**" access="ROLE_USER" />
<openid-login />
</http>
你應該注冊一個OpenID供應器(比如myopenid.com),然后把用戶信息添加到你的內存<user-service>中:
<user name="http://jimi.hendrix.myopenid.com/" password="notused" authorities="ROLE_USER" />
你應該可以使用myopenid.com網站登錄來進行驗證了。
2.3.5. 添加你自己的filter
如果你以前使用過Spring Security,你應該知道這個框架里維護了一個過濾器鏈,來提供它的服務。 你也許想把你自己的過濾器添加到鏈條的特定位置,或者讓已存在的過濾器,使用特定的版本。 你如何在命名空間配置里實現這些功能呢?過濾器鏈現在已經不能之間看到了。
過濾器順序在使用命名空間的時候是被嚴格執行的。 每個Spring Security過濾器都實現了Spring的Ordered接口,這些過濾器在初始化的時候先被排好序了。 標準的過濾器在命名空間里都有自己的假名:
Table 2.1. 標準過濾器假名和順序
Alias Filter Class
CHANNEL_FILTER ChannelProcessingFilter
CONCURRENT_SESSION_FILTER ConcurrentSessionFilter
SESSION_CONTEXT_INTEGRATION_FILTER HttpSessionContextIntegrationFilter
LOGOUT_FILTER LogoutFilter
X509_FILTER X509PreAuthenticatedProcessigFilter
PRE_AUTH_FILTER Subclass of AstractPreAuthenticatedProcessingFilter
CAS_PROCESSING_FILTER CasProcessingFilter
AUTHENTICATION_PROCESSING_FILTER AuthenticationProcessingFilter
BASIC_PROCESSING_FILTER BasicProcessingFilter
SERVLET_API_SUPPORT_FILTER classname
REMEMBER_ME_FILTER RememberMeProcessingFilter
ANONYMOUS_FILTER AnonymousProcessingFilter
EXCEPTION_TRANSLATION_FILTER ExceptionTranslationFilter
NTLM_FILTER NtlmProcessingFilter
FILTER_SECURITY_INTERCEPTOR FilterSecurityInterceptor
SWITCH_USER_FILTER SwitchUserProcessingFilter
你可以把你自己的過濾器添加到隊列中,使用custom-filter元素,使用這些名字中的一個,來指定你的過濾器應該出現的位置:
<beans:bean id="myFilter" class="com.mycompany.MySpecialAuthenticationFilter">
<custom-filter position="AUTHENTICATION_PROCESSING_FILTER"/>
</beans:bean>
你還可以使用after 或 before屬性,如果你想把你的過濾器添加到隊列中另一個過濾器的前面或后面。可以使用"FIRST" 或 "LAST"來指定你想讓你的過濾器分別出現在隊列元素的前面或后面。
2.3.6. 防止Session固定攻擊
Session固定攻擊是一個潛在危險,當一個惡意攻擊者可以創建一個session訪問一個網站的時候,然后說服另一個用戶登錄到同一個會話上(比如,發送給他們一個包含了session標識參數的鏈接)。 Spring Security通過在用戶登錄時,創建一個新session來防止這個問題。 如果你不需要保護,或者它與其他一些需求沖突,你可以通過使用<http>中的session-fixation-protection屬性來配置它的行為,它有三個選項
*
migrateSession - 創建一個新session,把原來session中所有屬性復制到新session中。這是默認值。
*
none - 什么也不做,繼續使用原來的session。
*
newSession - 創建一個新的“干凈的”session,不會復制session中的數據。
2.3.7. 設置自定義AuthenticationEntryPoint
如果你不使用命名空間里的表單登錄,OpenID或基本身份驗證,你也許想定義個驗證過濾器和入口點,使用傳統的bean語法,把他們鏈接到命名空間里。 你可以像Section 2.3.5, “添加你自己的filter”里解釋的那樣,添加過濾器。 對應的AuthenticationEntryPoint可以使用<http>中的entry-point-ref進行設置。
CAS例子,是一個在命名空間里使用自定義bean的好例子,包括這個語法。如果你不熟悉驗證入口點,可以看看技術縱覽章節中的討論。
2.4. 保護方法
Spring Security 2.0大幅改善了對你的服務層方法添加安全。 如果你使用Java 5或更高版本,還支持JSR-250的安全注解,同框架提供的@secured注解相似。 你可以為單個bean提供安全控制,通過使用intercept-methods元素裝飾bean聲明,或者你可以使用AspectJ方式的切點來控制實體服務層里的多個bean。
2.4.1. <global-method-security>元素
這個元素用來在你的應用程序中啟用基于安全的注解(通過在這個元素中設置響應的屬性),也可以用來聲明將要應用在你的實體application context中的安全切點組。 你應該只定義一個<global-method-security>元素。 下面的聲明同時啟用兩種類型的注解:
<global-method-security secured-annotations="enabled" jsr250-annotations="enabled"/>
2.4.1.1. 使用protect-pointcut添加安全切點
protect-pointcut是非常強大的,它讓你可以用簡單的聲明對多個bean的進行安全聲明。 參考下面的例子:
<global-method-security>
<protect-pointcut expression="execution(* com.mycompany.*Service.*(..))" access="ROLE_USER"/>
</global-method-security>
這樣會保護application context中的符合條件的bean的所有方法,這些bean要在com.mycompany包下,類名以"Service"結尾。 ROLE_USER的角色才能調用這些方法。 就像URL匹配一樣,指定的匹配要放在切點隊列的最前面,第一個匹配的表達式才會被用到。
2.5. 默認的AccessDecisionManager
這章假設你有一些Spring Security權限控制有關的架構知識。 如果沒有,你可以跳過這段,以后再來看,因為這章只是為了自定義的用戶設置的,需要在簡單基于角色安全的基礎上加一些客戶化的東西。
當你使用命名空間配置時,默認的AccessDecisionManager實例會自動注冊,然后用來為方法調用和web URL訪問做驗證,這些都是基于你設置的intercept-url和protect-pointcut權限屬性內容(和注解中的內容,如果你使用注解控制方法的權限)。
默認的策略是使用一個AffirmativeBased AccessDecisionManager ,以及RoleVoter 和AuthenticatedVoter。
2.5.1. 自定義AccessDecisionManager
如果你需要使用一個更復雜的訪問控制策略,把它設置給方法和web安全是很簡單的。
對于方法安全,你可以設置global-security里的access-decision-manager-ref屬性,用對應 AccessDecisionManager bean在application context里的id:
<global-method-security access-decision-manager-ref="myAccessDecisionManagerBean">
...
</global-method-security>
web安全安全的語法也是一樣,但是放在http元素里:
<http access-decision-manager-ref="myAccessDecisionManagerBean">
...
</http>
2.5.2. 驗證管理器
我們大概知道命名空間配置會自動為我們注冊一個驗證管理器bean。 這是一個Spring Security的ProviderManager類,如果你以前使用過框架,應該對它很熟悉了。
你也許想為ProviderManager注冊另外的AuthenticationProvider bean,你可以使用<custom-authentication-provider>元素實現。比如:
<bean id="casAuthenticationProvider"
class="org.springframework.security.providers.cas.CasAuthenticationProvider">
<security:custom-authentication-provider />
...
</bean>
另一個常見的需求是,上下文中的另一個bean可能需要引用AuthenticationManager。 這里有一個特殊的元素,可以讓你為AuthenticationManager注冊一個別名,然后你可以application context的其他地方使用這個名字。
<security:authentication-manager alias="authenticationManager"/>
<bean id="casProcessingFilter" class="org.springframework.security.ui.cas.CasProcessingFilter">
<security:custom-filter position="CAS_PROCESSING_FILTER"/>
<property name="authenticationManager" ref="authenticationManager"/>
...
</bean>
第三章 簡單的應用
這幾個網絡應用程序在項目中是可用的。為了避免大負荷的下載,僅僅“指南”和“連接”例子包含在發行文件中。你也可以自行生成其它例子的項目文件,或者你也可以通過MAVEN倉庫獲取war文件。正如入門文檔所描述的那樣,你可以獲得源代碼并很容易的利用maven編譯和部署它。
3.1. 指南的例子
指南的例子是個不錯的入門級應用。它將簡單的命名空間進行配置貫徹始終.
關于運行環境就不多說了,JDK1.4以上版本包含1.4.
讓我們深入的了解一下spring security里面的共享組組件吧
1.SecurityContextHolder,SecurityContext和Authentication對象,這里面最基本的對象是SecurityContextHolder. 這里將存儲應用程序的安全細節包括當前應用程序使用的principal細節,默認的SecurityContextHolder是以本地線程進行存儲細節的,這意味著在同一線程中安全上下文對方法總是可用的,即使安全上下文并不是明確的將請求分發給各個方法,使用本地線程對于principal的請求被處理時是非常安全的,當轉移請求的時候。當然,spring security會自動幫你保管所以你也無需擔心。有些應用程序卻并不適合使用本地線程,因為它們以特殊的線程方式運行。比如一個SWING的客戶端程序需要JAVA虛擬機所有線程共用一個安全上下文。在這種情況下你需要使用SecurityContextHolder.MODE_GLOBAL。其它程序也希望由相同的安全標識線程產生其它線程,可以用SecurityContextHolder.MODE_INHERITABLETHREADLOCAL來實現上述要求。你有兩種方式來改變默認的SecurityContextHolder.MODE_THREADLOCAL 模式。第一種是設置系統屬性,作為選擇可在SecurityContextHolder中調用一個靜態方法。絕大多數的應用程序不需要改變,如果你想改變請在JAVADOC中獲取更多的內容。
在SecurityContextHolder內部我們存儲的當前principal細節與應用程序相結合,Spring Security使用一個Authentication對象去描述這些信息,同時你也不需要自己在去創建一個Authentication對象了,Spring Securtity為每一個用戶都提供Authentication對象查詢,你可以使用以下的程序代碼塊----在你程序的任何地方。
Object obj = SecurityContextHolder.getContext().getAuthentication().getPrincipal();

if (obj instanceof UserDetails)
{
String username = ((UserDetails)obj).getUsername();

} else
{
String username = obj.toString();
}
上面的代碼介紹了許多有趣的關系和鍵值對象,首先你將注意到在SecurityContextHolder 和Authentication之間有一個中間媒介,SecurityContextHolder.getContext()方法事實上返回一個SecurityContext。
5.2.2. The UserDetailsService
通過上面的代碼片段你可以從Authentication對象中獲得一個principal。principal也是一個對象,大多數情況你都能從UserDetails中得到它的映射。
在Spring Security中UserDetails是一個重要的接口,它描述一個principal,在可擴展的且特殊的應用方式。你可以將UserDetails想象成一個適配器在你的數據庫與Spring Security內部需要的 SecurityContextHolder之間。你完全可以在UserDetails中找到你應用程序中提供的原始對象的映射,因此你可以調用諸如getEmail(), getEmployeeNumber()等等的業務邏輯方法。現在你可能感到奇怪,我在何時提供了一個UserDetails對象?我是怎么做到的?誰提供的?最簡短的解釋就是有一個叫UserDetailsService的接口,它僅僅只有一個方法接受String類型的username參數并返回一個UserDetails。大部分的認證模塊都會委托給UserDetailsService成為認證處理的一部分。UserDetailsService被用來構建存儲在SecurityContextHolder里的Authentication對象。有個好消息是我們提供了許多UserDetailsService的實現,包括用于內存處理和JDBC處理的實現。大多數用戶趨于實現他們自己的實現,盡管,在DAO中僅僅是一些簡單的實現來描述雇傭者,客戶信息或者其它企業級應用。
5.2.3. GrantedAuthority
除了principal以外,由Authentication 提供的另外一個非常重要的方法就是getAuthorities( ),這個方法提供一個GrantedAuthority對象數組,一個GrantedAuthority是由principal準許的一個授權,通常這些授權是一些"角色",比如 ROLE_ADMINISTRATOR或者ROLE_HR_SUPERVISOR,這些角色稍后將配置于web授權、方法授權和業務邏輯對象授權。Spring Security的其它部分能夠解釋這些授權,并將他們顯示出來。權限類通常由UserDetailsService加載!通常GrantedAuthority在大部分應用程序中是允許的。但是他們并不是單獨的給特定的業務邏輯對象,因此,你不用給你的雇員業務邏輯對象單獨提供角色,假如這里有數以千計的授權,你內存會很快溢出的,或者說為你一個用戶授權要花費很長的一段時間。
當然Spring Security為這些公共的需求特別設計了句柄,不過你最好使用你自己項目的對象安全容器去替代掉。有時,你需要在HTTP請求間對SecurityContext進行存儲,其它時間principal會對每個請求重新認證,盡管大多數情況下它是會被存儲的。HttpSessionContextIntegrationFilter將負責在HTTP請求間對一個SecurityContext進行存儲。根據類型的名字,HttpSession 會存儲這些信息,你完全沒有必要使用HttpSession 存儲安全信息,通常都用SecurityContextHolder來代替。
5.2.4. Summary
Spring Security主要組件如下:
1.SecurityContextHolder, 提供各種各樣的類型登錄SecurityContext
2.SecurityContext,控制安全信息的請求認證
3.HttpSessionContextIntegrationFilter,在web請求間將SecurityContext存儲在HttpSession中
4.Authentication,描述一個具有spring security風格的principal
5.GrantedAuthority,映射程序范圍內許可的principal
6.UserDetails,從你的應用程序的DAOS中獲取有用的信息來構建一個Authentication對象
7.UserDetailsService,當傳進一個string類型的username時創建一個UserDetails.
5.3. Authentication
一個典型的網絡應用認證處理
1.你訪問一個主頁,然后點擊一個鏈接。
2.服務器收到一個請求,確定你訪問了一個被保護的請求。
3.如果你沒有被認證,服務器端會返回一個指示請求要求你認證。請求可以是一個HTTP請求代碼,也可以是一個重定向的頁面。
4.依賴認證機制,你的瀏覽器也會重導向到特定頁面,所以你可以填寫表單,或者瀏覽器以各種方式找回你的唯一標識。
5.瀏覽器回回傳一個請求給服務器,可能是個POST請求包含你填寫的表單內容,或者一個HTTP頭包含你的認證細節。
6.服務器端將會判斷發送過來的認證信息是否有效,如果有效,下一步將會啟動。如果無效,你的瀏覽器將會再次詢問(回到第三步)
7.如果你有訪問權限將能夠訪問相應的資源,否則將會返回一個403錯誤。
5.3.1. ExceptionTranslationFilter
ExceptionTranslationFilter是一個spring security 過濾器,它負責檢測所有spring secutrity所拋出的異常,這些異常通常由主要為認證服務的AbstractSecurityInterceptor拋出,我們將在下一部分討論AbstractSecurityInterceptor。但是現在我們需要知道它產生JAVA錯誤,并不知道HTTP的內容和如何去驗證一個principal,代替ExceptionTranslationFilter提供這些服務,并負責返回403的錯誤代碼(如果principal已經被認證而且缺乏足夠的登錄按照上述步驟7)或者登錄一個AuthenticationEntryPoint(如果principal沒有被認證因此我們需要返回第三步)。
5.3.2. AuthenticationEntryPoint
AuthenticationEntryPoint 負責上述步驟的第三步,你可以想象,每個網絡應用程序都將會有個默認的認證策略,每個主要的認證系統都將有它自己的AuthenticationEntryPoint實現,他們用來描述步驟3中的動作,在你的瀏覽器決定提交你的認證證書后,這需要服務器端需要一些類似于收集這些認證的細節的東西。現在我們已經到了第六步了。在Spring Security中有一個專用名為從用戶參數收集認證細節的函數,那個名字就是authentication mechanism。從用戶參數中獲得相應的認證細節以后,一個認證請求對象被構建,然后將會指向一個AuthenticationProvider。
5.3.3. AuthenticationProvider
歡迎到
http://www.tutu6.com來看看