【總結-含源碼】Spring Security學習總結一(補命名空間配置)
Posted on 2008-08-20 10:25 tangtb 閱讀(43111) 評論(27) 編輯 收藏 所屬分類: Spring 、 Spring SecuritySpring Security學習總結一
在認識Spring Security以前,全部的權限驗證邏輯都混雜在業務邏輯中,用戶的每一個操做之前可能都須要對用戶是否有進行該項 操做的權限進行判斷,來達到認證受權的目的。相似這樣的權限驗證邏輯代碼被分散在系統的許多地方,難以維護。AOP(Aspect Oriented Programming)和Spring Security爲咱們的應用程序很好的解決了此類問題,正如系統日誌,事務管理等這些系統級的服務同樣,咱們應 該將它做爲系統一個單獨的」切面」進行管理,以達到業務邏輯與系統級的服務真正分離的目的,Spring Security將系統的安全邏輯 從業務中分離出來。html
本文代碼運行環境:
java
JDK6.0linux
spring-framework-2.5.4程序員
spring-security-2.0.0web
JavaEE5算法
Web容器:spring
Apache Tomcat6.0數據庫
IDE工具:數組
Eclipse3.3+MyEclipse6.5瀏覽器
操做系統:
Linux(Fedora 8)
這只是我我的的學習總結而已,還請高手們指出本文的不足之處。
一 Spring Security 簡介
這裏提到的Spring Security也就是被你們廣爲熟悉的Acegi Security,2007年末Acegi Security正式成爲Spring Portfolio項目,並改名爲Spring Security。Spring Security是一個能夠爲基於Spring的企業應用系統提供描述性安全訪問控制解決方案的安全框架。 它提供了一組能夠在Spring應用上下文中配置的Bean,充分利用了Spring IoC(依賴注入,也稱控制反轉)和AOP(面向切面編程)功能,爲應用系統提供聲明式的安全訪問控制功能,減小了爲企業系統安全控制編寫大量重複代碼的工做。
經過在許多項目中實踐應用以及社區的貢獻,現在的Spring Security已經成爲Spring Framework下最成熟的安全系統,它爲咱們提供了強大而靈活的企業級安全服務,如:
Ø 認證受權機制
Ø Web資源訪問控制
Ø 業務方法調用訪問控制
Ø 領域對象訪問控制Access Control List(ACL)
Ø 單點登陸(Central Authentication Service)
Ø X509認證
Ø 信道安全(Channel Security)管理等功能
當保護Web資源時,Spring Security使用Servlet 過濾器來攔截Http請求進行身份驗證並強制安全性,以 確保WEB資源被安全的訪問。以下圖是Spring Security的主要組件圖(摘自《Spring in Action》):
圖1 Spring Security的基本組件
不管是保護WEB資源仍是保護業務方法或者領域對象,Spring Security都的經過上圖中的組件來完成 的。本文主要闡述如何使用Spring Security對WEB應用程序的資源進行安全訪問控制,並經過一個簡單的 實例來對Spring Security提供的各類過濾器的功能和配置方法進行描述。
二 保護Web資源
Spring Security提供了不少的過濾器,它們攔截Servlet請求,並將這些請求轉交給認證處理過濾器和訪問決策過濾器進行處理,並強制安全性,認證用戶身份和用戶權限以達到保護Web資源的目的。對於Web資源咱們大約能夠只用6個過濾器來保護咱們的應用系統,下表列出了這些安全過濾器的名稱做用以及它們在系統中的執行順序:
過 濾 器 |
做 用 |
通道處理過濾器 |
確保請求是在安全通道(HTTP和HTTPS)之上傳輸的 |
認證處理過濾器 |
接受認證請求,並將它們轉交給認證管理器進行身份驗證 |
CAS處理過濾器 |
接受CAS服務票據,驗證Yale CAS(單點登陸)是否已經對用戶進行了認證 |
HTTP基本受權過濾器 |
處理使用HTTP基本認證的身份驗證請求 |
集成過濾器 |
處理認證信息在請求間的存儲(好比在HTTP會話中) |
安全強制過濾器 |
確保用戶己經認證,而且知足訪問一個受保護Web資源的權限需求 |
接下來,經過一個實例來講明它們的具體使用方法和如何在Spring中進行配置。
1 創建Spring Security項目
首先在MyEclipse中建立一個Web Project,並使用MyEclipse工具導入Spring項目的依賴JAR包,並生成默認的,這裏暫時不會用到這個文件,本文只是經過一個簡單的實例來講明如何配置使用Spring Security,不會涉及到數據庫,而是使用一個用戶屬性(users.properties)文件來保存用戶信息(包括用戶名,密碼及相應的權限),但在實際的項目中,咱們不多會這樣作,而是應該把用戶信息存在數據庫中,下一篇文章中將會詳細介紹並用到這個文件來配置Hibernate,這裏咱們保留它。
如今還須要爲項目導入Spring Security的JAR包,它沒有包括在Spring Framework中,你能夠從http://www.springframework.org/download/下載,並將spring-security-core-2.0.0.jar(這是核心代碼庫)和spring-security-core-tiger-2.0.0.jar(和annotation有關的,好比使用註解對方法進行安全訪問控制,在下一篇中會用到)拷貝到項目的lib目錄下,其中也包括兩個實例(tutorial和contacts),而且兩個實例中都包括瞭如何使用Spring 2.0的命名空間來配置Spring Security,不管你對Spring 2.0命名空間的使用是否瞭解,它將使咱們的配置文件大大縮短,簡化開發,提升生產效率。到此,咱們的Spring Security項目就建好了,項目目錄結構以下圖所示:
圖2 項目目錄結構
2 配置web.xml
Spring Security使用一組過濾器鏈來對用戶進行身份驗證和受權。首先,在web.xml文件中添加FilterToBeanProxy過濾器配置:
2 <filter-name>springSecurityFilterChain</filter-name>
3 <filter-class>
4 org.springframework.security.util.FilterToBeanProxy
5 </filter-class>
6 <init-param>
7 <param-name>targetClass</param-name>
8 <param-value>
9 org.springframework.security.util.FilterChainProxy
10 </param-value>
11 </init-param>
12 </filter>
13
org.springframework.security.util.FilterToBeanProxy實現了Filter接口,它經過調用WebapplicationContextUtils類的getWebApplicationnContext(servletContext)方法來獲取Spring的應用上下文句柄,並經過getBean(beanName)方法來獲取Spring受管Bean的對象,即這裏targetClass參數配置的Bean,並經過調用FilterChain
Proxy的init()方法來啓動Spring Security過濾器鏈進行各類身份驗證和受權服務(FilterChainProxy類也是實現了Filter接口),從而將過濾功能委託給Spring的FilterChainProxy受管Bean(它維護着一個處理驗證和受權的過濾器 列表,列表中的過濾器按照必定的順序執行並完成認證過程),這樣即簡化了web.xml文件的配置,又能充分利用 Spring的IoC功能來完成這些過濾器執行所須要的其它資源的注入。
當用戶發出請求,過濾器須要根據web.xml配置的請求映射地址來攔截用戶請求,這時Spring Security開始工做,它會驗證你的身份以及當前請求的資源是否與你擁有的權限相符,從而達到保護Web資源的功能,下面是本例所要過濾的用戶請求地址:
2
3 <filter-name>springSecurityFilterChain</filter-name>
4
5 <url-pattern>/j_spring_security_check</url-pattern>
6
7 </filter-mapping>
8
9 <filter-mapping>
10
11 <filter-name>springSecurityFilterChain</filter-name>
12
13 <url-pattern>/*</url-pattern>
14
15 </filter-mapping>
提示: /j_spring_security_check是Spring Security默認的進行表單驗證的過濾地址,你也能夠修改成別的名稱,可是須要和 applicationContext-security.xml中相對應,固然還會涉及到其它一些默認值(多是一個成員變量,也多是別的請 求地址),在下文咱們將看到,建議你在閱讀此文的同時,應該參照Spring Security項目的源代碼,便於你更好的理解。 |
3 配置applicationContext-security.xml
3.1 FilterChainProxy過濾器鏈
FilterChainProxy會按順序來調用一組filter,使這些filter即能完成驗證受權的本質工做,又能享用Spring Ioc的功能來方便的獲得其它依賴的資源。FilterChainProxy配置以下:
class="org.springframework.security.util.FilterChainProxy">
2 <property name="filterInvocationDefinitionSource">
3 <value><![CDATA[
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
4 PATTERN_TYPE_APACHE_ANT
/**=httpSessionContextIntegrationFilter,logoutFilter,
5 authenticationProcessingFilter,securityContextHolderAwareRequestFilter,
6 rememberMeProcessingFilter,anonymousProcessingFilter,exceptionTranslationFilter,
7 filterSecurityInterceptor
8 ]]></value>
9 </property>
10 </bean>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON 定義URL在匹配以前必須先轉爲小寫,PATTERN_TYPE_APACHE_ANT 定義了使用Apache ant的匹配模式,/**定義的將等號後面的過濾器應用在那些URL上,這裏使用所有URL過濾,每一個過濾器之間都適用逗號分隔,它們按照必定的順序排列。
提示: 特別須要注意的是,即便你配置了系統提供的全部過濾器,這個過濾器鏈會很長,可是千萬不要使用換行,不然它們不會正常工做, 容器甚至不能正常啓動。 |
下面根據FilterChainProxy的配置來介紹各個過濾器的配置,各個過濾器的執行順序如以上配置。
首先是通道處理過濾器,若是你須要使用HTTPS,這裏咱們就使用HTTP進行傳輸,因此不須要配置通道處理過濾器,而後是集成過濾器,配置以下:
2
3 class="org.springframework.security.context.HttpSessionContextIntegrationFilter"/>
httpSessionContextIntegrationFilter是集成過濾器的一個實現,在用戶的一個請求過程當中,用戶的認證信息經過SecurityContextHolder(使用ThreadLoacl實現)進行傳遞的,全部的過濾器都是經過SecurityContextHolder來獲取用戶的認證信息,從而在一次請求中全部過濾器都能共享Authentication(認證),減小了HttpRequest參數的傳送,下面的代碼是從安全上下文的獲取Authentication對象的方法:
2
3 Authentication authentication = context.getAuthentication();
但 是,ThreadLoacl不能跨越多個請求存在,因此,集成過濾器在請求開始時從Http會話中取出用戶認證信息並建立一個 SecurityContextHolder將Authentication對象保存在其中,在請求結束以後,在從 SecurityContextHolder中獲取Authentication對象並將其放回Http會話中,共下次請求使用,從而達到了跨越多個請求 的目的。集成過濾器還有其它的實現,能夠參考相關文檔。
提示: 集成過濾器必須在其它過濾器以前被使用。 |
logoutFilter(退出過濾器) ,退出登陸操做:
2
3 class="org.springframework.security.ui.logout.LogoutFilter">
4
5 <constructor-arg value="/index.jsp"/>
6
7 <constructor-arg>
8
9 <list>
10
11 <!-- 實現了LogoutHandler接口(logout方法) -->
12
13 <ref bean="rememberMeServices"/>
14
15 <bean class="org.springframework.security.ui.logout.SecurityContextLogoutHandler"/>
16
17 </list>
18
19 </constructor-arg>
20
21 </bean>
LogoutFilter的構造函數須要兩個參數,第一個是退出系統後系統跳轉到的URL,第二個是一個LogoutHandler類型的數組,這個數組裏的對象都實現了LogoutHandler接口,並實現了它的logout方法,用戶在發送退出請求後,會一次執行LogoutHandler數組的對象並調用它們的 logout方法進 行一些後續的清理操做,主要是從SecurityContextHolder對象中清楚全部用戶的認證信息(Authentication對象),將用戶 的會話對象設爲無效,這些都時由SecurityContextLogoutHandler來完成。LogoutFilter還會清除Cookie記錄, 它由另一個Bean來完成(RememberMeServices)。
<ref bean="rememberMeServices"/>標記指向了咱們另外配置的一個Bean:
class="org.springframework.security.ui.rememberme.TokenBasedRememberMeServices"
2 p:key="springsecurity"
3 p:userDetailsService-ref="userDetailsService"/>
TokenBasedRememberMeServices繼承自系統的AbstractRememberMeServices抽象類(實現了RememberMeServices和 LogoutHandler兩個接口), RememberMeServices接口的loginSuccess方法負責在用戶成功登陸以後將用戶的認證信息存入Cookie中,這個類在後續的過濾器執行過程當中也會被用到。
另外一個userDetailsService屬性也是指向了咱們配置的Bean, 它負責從數據庫中讀取用戶的信息,這個類的詳細配置將在後面的部分詳細介紹,這裏只是簡單的認識一下。
過濾器鏈的下個配置的過濾器是authenticationProcessingFilter(認證過程過濾器),咱們使用它來處理表單認證,當接受到與filterProcessesUrl所定義相同的請求時它開始工做:
2
3 class="org.springframework.security.ui.webapp.AuthenticationProcessingFilter"
4
5 p:authenticationManager-ref="authenticationManager"
6 p:authenticationFailureUrl="/login.jsp?login_error=1"
7 p:defaultTargetUrl="/default.jsp"
8 p:filterProcessesUrl="/j_spring_security_check"
9 p:rememberMeServices-ref="rememberMeServices"/>
下面列出了認證過程過濾器配置中各個屬性的功能:
1.authenticationManager 認證管理器
2.authenticationFailureUrl 定義登陸失敗時轉向的頁面
3.defaultTargetUrl 定義登陸成功時轉向的頁面
4.filterProcessesUrl 定義登陸請求的地址(在web.xml中配置過)
5.rememberMeServices 在驗證成功後添加cookie信息
這裏也用到了rememberMeServices,若是用戶認證成功,將調用RememberMeServices的loginSuccess方法將用戶認證信息寫入Cookie中,這裏也能夠看到使用IoC的好處。
決定用戶是否有權限訪問 受保護資源的第一步就是要肯定用戶的身份,最經常使用的方式就是用戶提供一個用戶名和密碼以確認用戶的身份是否合法,這一步就是由認證過程過濾器調用 authenticationManager(認證管理器)來完成的。org.springframework.security.AuthenticationManager接口定義了一個authenticate方法,它使用Authentication做爲入口參數(只包含用戶名和密碼),並在驗證成功後返回一個完整的Authentication對象(包含用戶的權限信息GrantedAuthority數組對象),authenticationProcessingFilter(認證過程過濾器)會將這個完整的Authentication對象存入SecurityContext中,若是認證失敗會拋出一個AuthenticationException並跳轉到authenticationFailureUrl 定義的URL。認證管理其配置以下:
2
3 class="org.springframework.security.providers.ProviderManager"
4 p:sessionController-ref="concurrentSessionController">
5 <property name="providers">
6 <list>
7 <ref bean="daoAuthenticationProvider"/>
8 <bean
9
10 class="org.springframework.security.providers.anonymous.AnonymousAuthenticationProvider"
11 p:key="springsecurity"/>
12 <bean
13
14 class="org.springframework.security.providers.rememberme.RememberMeAuthenticationProvider"
15 p:key="springsecurity"/>
16 </list>
17 </property>
18 </bean>
正如在配置中看到的同樣,系統使用org.springframework.security.providers.ProviderManager(提供者管理器)類做爲認證管理器的一個實現,事實上這個類是繼承自實現了AuthenticationManager接口的 AbstractAuthenticationManager類。須要注意的是ProviderManager(提供者管理器)本身並不實現身份驗證,而是把這項工做交給了多個認證提供者(提供者集合)或者說的多個認證來源。
提示: Spring Security爲咱們提供的全部認證提供者實現都是org.springframework.security.providers .AuthenticationProvider 接口的實現類,它們都實現了此接口的authenticate方法,若是你正在看源代碼,會發現這個authenticate方法事實上和Authe nticationManager(認證管理器)接口的authenticate方法徹底同樣。 |
providers屬性定義了提供者管理器的集合,ProviderManager(提供者管理器)逐一遍歷這個認證提供者的集合並調用提供者的authenticate方法,若是一個提供者認證失敗會嘗試另一個提供者直到某一個認證提供者可以成功的驗證該用戶的身份,以保證獲取不一樣來源的身份認證。下面表格列出了系統提供的一些認證提供者:
提 供 者 |
做 用 |
DaoAuthenticationProvider |
從數據庫中讀取用戶信息驗證身份 |
AnonymousAuthenticationProvider |
匿名用戶身份認證 |
RememberMeAuthenticationProvider |
已存cookie中的用戶信息身份認證 |
AuthByAdapterProvider |
使用容器的適配器驗證身份 |
CasAuthenticationProvider |
根據Yale中心認證服務驗證身份, 用於實現單點登錄 |
JaasAuthenticationProvider |
從JASS登錄配置中獲取用戶信息驗證身份 |
RemoteAuthenticationProvider |
根據遠程服務驗證用戶身份 |
RunAsImplAuthenticationProvider |
對身份已被管理器替換的用戶進行驗證 |
X509AuthenticationProvider |
從X509認證中獲取用戶信息驗證身份 |
TestingAuthenticationProvider |
單元測試時使用 |
從上面的表中能夠看出,系統爲咱們提供了不一樣的認證提供者,每一個認證提供者會對本身指定的證實信息進行認證,如DaoAuthenticationProvider僅對UsernamePasswordAuthenticationToken這個證實信息進行認證。
在實際項目中,用戶的身份和權限信息可能存儲在不一樣的安全系統中(如數據庫,LDAP服務器,CA中心)。
做爲程序員,咱們能夠根據須要選擇不一樣的AuthenticationProvider(認證提供者)來對本身的系統提供認證 服務。
這裏咱們着重介紹DaoAuthenticationProvider,它從數據庫中讀取用戶信息驗證身份,配置以下:
class="org.springframework.security.providers.dao.DaoAuthenticationProvider"
2 p:passwordEncoder-ref="passwordEncoder"
3 p:userDetailsService-ref="userDetailsService"/>
4 <bean id="passwordEncoder"
5 class="org.springframework.security.providers.encoding.Md5PasswordEncoder"/>
還記得前面配置的RememberMeServices嗎?它也有一個和DaoAuthenticationProvider一樣的屬性userDetailsService,這是系統提供的一個接口(org.springframework.security.userdetails.UserDetailsService),在這裏咱們把它單獨提出來進行介紹。
首先咱們須要瞭解Spring Security爲咱們提供的另一個重要的組件,org.springframework.security.userdetails .UserDetails接口,它表明一個應用系統的用戶,該接口定義與用戶安全信息相關的方法:
String getUsername():獲取用戶名;
String getPassword():獲取密碼;
boolean isAccountNonExpired():用戶賬號是否過時;
boolean isAccountNonLocked():用戶賬號是否鎖定;
boolean isCredentialsNonExpired():用戶的憑證是否過時;
boolean isEnabled():用戶是否處於激活狀態。
當以上任何一個判斷用戶狀態的方法都返回false時,用戶憑證就被視爲無效。UserDetails接口還定義了獲取用戶權限信息的getAuthorities()方法,該方法返回一個GrantedAuthority[]數組對象,GrantedAuthority是用戶權限信息對象,這個對象中定義了一個獲取用戶權限描述信息的getAuthority()方法。
UserDetails便可從數據庫中返回,也能夠從其它如LDAP中返回,這取決與你的系統中使用什麼來存儲用戶信息和權限以及相應的認證提供者。這裏咱們只重點介紹DaoAuthenticationProvider(從數據庫中獲取用戶認證信息的提供者),本人水平有限,在項目中尚未機會用到其它提供者。說到這裏,這個封裝了用戶詳細信息的UserDetails該從哪兒獲取呢?這就是咱們接下來要介紹的UserDetailsService接口,這個接口中只定義了惟一的UserDetails loadUserByUsername(String username)方法,它經過用戶名來獲取整個UserDetails對
象。
看到這裏你可能會有些糊塗,由於前面提到的Authentication對象中也存放了用戶的認證信息,須要注意Authentication對象纔是Spring Security使用的進行安全訪問控制用戶信息安全對象。實際上,Authentication對象有未認證和已認證兩種狀態,在做爲參數傳入認證管理器(AuthenticationManager)的authenticate方法時,是一個未認證的對象,它從客戶端獲取用戶的身份信息(如用戶名,密碼),能夠是從一個登陸頁面,也能夠從Cookie中獲取,並由系統自動構形成一個Authentication對象。而這裏提到的UserDetails表明一個用戶安全信息的源(從數據庫,LDAP服務器,CA中心返回),Spring Security要作的就是將這個未認證的Authentication對象和UserDetails進行匹配,成功後將UserDetails中的用戶權限信息拷貝到Authentication中組成一個完整的Authentication對象,共其它組件共享。
這樣,咱們就能夠在系統中獲取用戶的相關信息了,須要使用到Authentication對象定義的Object getPrincipal()方法,這個方法返回一個Object類型的對象,一般能夠將它轉換爲UserDetails,從而能夠獲取用戶名,密碼以及權限等信息。代碼以下:
2
3 GrantedAuthority[] authority = details.getAuthorities();
前面介紹了DaoAuthenticationProvider,它能夠從數據庫中讀取用戶信息,一樣也能夠從一個用戶屬性文件中讀取,下一篇文章中咱們在介紹如何從數據庫中讀取用戶信息,固然還會涉及到更深刻的東西,好比根據本身系統的須要自定義UserDetails和UserDetailsService,這個只是讓你對整個系統有個簡單的瞭解,因此咱們使用用戶屬性文件(users.properties)來存儲用戶信息:
2
3 user1=user1,ROLE_USER
4
5 user2=user2,ROLE_USER
6
7 user3=user3,disabled,ROLE_USER
配置userDetailsService:
2
3 class="org.springframework.security.userdetails.memory.InMemoryDaoImpl">
4 <property name="userProperties">
5 <bean class="org.springframework.beans.factory.config.PropertiesFactoryBean"
6 p:location="/WEB-INF/users.properties"/>
7 </property>
8 </bean>
InMemoryDaoImpl類是UserDetailsService接口的一個實現,它從屬性文件裏讀取用戶信息,Spring Security使用一個屬性編輯器將用戶信息爲咱們組織成一個org.springframework.security.userdetails.memory.UserMap類的對象,咱們也能夠直接爲它提供一個用戶權限信息的列表,詳見applicationContext-security.xml配置文件。
UserMap字符串的每一行都用鍵值對的形式表示,前面是用戶名,而後是等號,後面是賦予該用戶的密碼/權限等信息,它們使用逗號隔開。好比:
定義了一個名爲admin的用戶登陸密碼爲admin,該用戶擁有ROLE_SUPERVISOR權限,再如users.properties文件中配置的名爲user3的用戶登陸密碼爲user3,該用戶擁有ROLE_USER權限,disabled定義該用戶不可用,爲被激活(UserDetails 的isEnabled方法)。
即便是系統的開發者或者說是最終用戶,都不該該看到系統中有明文的密碼。因此,Spring Security考慮的仍是很周到的,爲咱們提供的密碼加密的功能。正如你在Dao認證提供者(DaoAuthenticationProvider)中看到的,passwordEncoder屬性配置的就是一個密碼加密程序(密碼編碼器)。這裏咱們使用MD5加密,能夠看
配置文件中的scott用戶,你還能看出他的密碼是什麼嗎?固然這裏只是演示功能,其它用戶仍是沒有改變, 你能夠本身試試。系統爲咱們提供了一些經常使用的密碼編碼器(這些編碼器都位於org.springframework.secu rity.providers.encoding包下):
PlaintextPasswordEncoder(默認)——不對密碼進行編碼,直接返回未經改變的密碼;
Md4PasswordEncoder ——對密碼進行消息摘要(MD4)編碼;
Md5PasswordEncoder ——對密碼進行消息摘要(MD5)編碼;
ShaPasswordEncoder ——對密碼進行安全哈希算法(SHA)編碼。
你能夠根據須要選擇合適的密碼編碼器,你也能夠設置編碼器的種子源(salt source)。一個種子源爲編碼提供種子(salt),或者稱編碼的密鑰,這裏再也不贅述。
這裏附加介紹了很多東西,但願你尚未忘記在AuthenticationManager(認證管理器)中還配置了一個名爲sessionController的Bean,這個Bean能夠阻止用戶在進行了一次成功登陸之後在進行一次成功的登陸。在 applicationContext-security.xml配置文件添加sessionController的配置:
2
3 class="org.springframework.security.concurrent.ConcurrentSessionControllerImpl"
4 p:maximumSessions="1"
5 p:exceptionIfMaximumExceeded="true"
6 p:sessionRegistry-ref="sessionRegistry"/>
7 <bean id="sessionRegistry"
8
9 class="org.springframework.security.concurrent.SessionRegistryImpl"/>
maximumSessions屬性配置了只容許同一個用戶登陸系統一次,exceptionIfMaximumExceeded屬性配置了在進行第二次登陸是是否讓第一次登陸失效。這裏設置爲true不容許第二次登陸。要讓此功能生效,咱們還 須要在web.xml文件中添加一個監聽器,以讓Spring Security能獲取Session的生命週期事件,配置以下:
2 <listener-class>
3 org.springframework.security.ui.session.HttpSessionEventPublisher
4 </listener-class>
5 </listener>
HttpSessionEventPublisher類實現javax.servlet.http.HttpSessionListener接口,在Session被建立的時候經過調用ApplicationContext的publishEvent(ApplicationEvent event)發佈HttpSessionCreatedEvent類型的事件,HttpSessionCreatedEvent類繼承自org.springframework.context.ApplicationEvent類的子類 HttpSessionApplicationEvent抽象類。
concurrentSessionController使用sessionRegistry來完成對發佈的Session的生命週期事件的處理,org.springframework.security.concurrent.SessionRegistryImpl(實現了SessionRegistry接口), SessionRegistryImpl類還實現了Spring Framework 的事件監聽org.springframework.context.Application Listener接口,並實現了該接口定義的onApplicationEvent(ApplicationEvent event)方法用於處理Applic ationEvent類型的事件,若是你瞭解Spring Framework的事件處理,那麼這裏你應該能夠很好的理解。
認證管理器到此介紹完畢了,認證過程過濾器也介紹完了,接下來咱們繼續介紹過濾器鏈的下一個過濾器
securityContextHolderAwareRequestFilter:
2 class="org.springframework.security.wrapper.SecurityContextHolderAwareRequestFilter"/>
這個過濾器使用裝飾模式(Decorate Model), 裝飾的HttpServletRequest對象。其Wapper是ServletRequest包裝類 HttpServletRequestWrapper的子類(如SavedRequestAwareWrapper或 SecurityContextHolderAwareRequestWrapper),附上獲取用戶權限信息,request參數,headers 和 cookies 的方法。
rememberMeProcessingFilter過濾器配置:
<bean id="rememberMeProcessingFilter"
class="org.springframework.security.ui.rememberme.RememberMeProcessingFilter"
p:authenticationManager-ref="authenticationManager"
p:rememberMeServices-ref="rememberMeServices"/>
當SecurityContextHolder中不存在Authentication用戶受權信息時,rememberMeProcessingFilter就會調用rememberMeServices 的autoLogin()方法從cookie中獲取用戶信息自動登陸。
anonymousProcessingFilter過濾器配置:
2 class="org.springframework.security.providers.anonymous.AnonymousProcessingFilter"
3 p:key="springsecurity"
4 p:userAttribute="anonymousUser,ROLE_ANONYMOUS"/>
若是不存在任何受權信息時,自動添加匿名用戶身份至SecurityContextHolder中,就是這裏配置的userAttribute,系統爲用戶分配一個ROLE_ANONYMOUS權限。
exceptionTranslationFilter(異常處理過濾器),該過濾器用來處理在系統認證受權過程當中拋出的異常,主要是處理AccessDeniedException和AuthenticationException兩個異常並根據配置跳轉到不一樣URL:
2 class="org.springframework.security.ui.ExceptionTranslationFilter"
3 p:accessDeniedHandler-ref="accessDeniedHandler"
4 p:authenticationEntryPoint-ref="authenticationEntryPoint"/>
5 <!-- 處理AccessDeniedException -->
6 <bean id="accessDeniedHandler"
7 class="org.springframework.security.ui.AccessDeniedHandlerImpl"
8 p:errorPage="/accessDenied.jsp"/>
9 <bean id="authenticationEntryPoint"
10 class="org.springframework.security.ui.webapp.AuthenticationProcessingFilterEntryPoint"
11 p:loginFormUrl="/login.jsp"
12 p:forceHttps="false"/>
accessDeniedHandler用於處理AccessDeniedException異常,當用戶沒有權限訪問當前請求的資源時拋出此異常,並跳轉自這裏配置的/accessDenied.jsp頁面。
authenticationEntryPoint(認證入口點),這裏定義了用戶登陸的頁面。系統爲咱們提供了3個認證入口點的實現:
認 證 入 口 點 |
做 用 |
BasicProcessingFilterEntryPoint |
經過向瀏覽器發送一個HTTP 401(未受權)消息,由瀏覽器彈出登陸對話框,提示用戶登陸 |
AuthenticationProcessingFilterEntryPoint |
將用戶重定向到一個基於HTML表單的登陸頁面 |
CasProcessingFilterEntryPoint |
將用戶重定向至一個Yale CAS登陸頁面 |
這裏咱們使用AuthenticationProcessingFilterEntryPoint認證入口點,提供給用戶一個友好的登陸界面,只是爲了給用戶更好的體驗。
filterSecurityInterceptor(過濾器安全攔截器), 該過濾器首先調用認證管理器來判斷用戶是否已被成功驗證,若是沒有被驗證則重定向到登陸界面。不然,從Authentication獲取用戶的權限信息, 而後從objectDefinitionSource中獲取URL所對應的權限,最後調用accessDecisionManager(訪問決策管理器) 來判斷用戶當前擁有的權限是否與當前受保護的URL資源對應的權限匹配,若是匹配就能夠訪問該URL資源,不然將拋出AccessDeniedException異常並返 回客戶端瀏覽器一個403錯誤(若是用戶定義了accessDenied頁面則會被重定向到該頁,見:異常處理過濾器 exceptionTranslationFilter中配置的accessDeniedHandler Bean),訪問決策管理的的工做機制將在隨後更詳細介紹,這裏先給出過濾器安全攔截器的配置以下:
2
3 class="org.springframework.security.intercept.web.FilterSecurityInterceptor"
4
5 p:authenticationManager-ref="authenticationManager"
6
7 p:accessDecisionManager-ref="accessDecisionManager">
8 <property name="objectDefinitionSource">
9 <value><![CDATA[
10
11 CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
12 PATTERN_TYPE_APACHE_ANT
13 /admins/**=ROLE_SUPERVISOR
14 /user/**=ROLE_USER,IS_AUTHENTICATED_REMEMBERED
15 /default.jsp=ROLE_USER,IS_AUTHENTICATED_REMEMBERED
16 /**=IS_AUTHENTICATED_ANONYMOUSLY
17 ]]></value>
18 </property>
19 </bean>
從配置能夠看出來,過濾 器安全攔截器用到了咱們前面配置的認證管理器,過濾器安全攔截器使用authenticationManager並調用它的providers(提供者列 表)來對用戶的身份進行驗證並獲取用戶擁有的權限。若是用戶被成功認證,過濾器安全攔截器將會使用accessDecisionManager(訪問決策管理器)來判斷已認證的用戶是否有權限訪問受保護的資源,這些受保護的資源由objectDefinitionSource屬性定義。
訪問決策管理器(accessDecisionManager):
2 class="org.springframework.security.vote.AffirmativeBased"
3 p:allowIfAllAbstainDecisions="false">
4 <property name="decisionVoters">
5 <list>
6 <bean class="org.springframework.security.vote.RoleVoter"/>
7 <bean class="org.springframework.security.vote.AuthenticatedVoter"/>
8 </list>
9 </property>
10 </bean>
身份驗證只是Spring Security安全機制的第一步,訪問決策管理器驗證用戶是否有權限訪問相應的資源(filterSecurityInterceptor中objectDefinitionSource屬性定義的訪問URL須要的屬性信息)。
org.springframework.security.AccessDecisionManager接口定義了用於驗證用戶是否有權限訪問受保護資源的decide方法,另外一個supports方法根據受保護資源的配置屬性(即訪問這些資源所需的權限)來判斷該訪問決策管理器是否能作出針對該資源的訪問決策。decide方法最終決定用戶有無訪問權限,若是沒有則拋出AccessDeniedException異常(面前也提到過,你應該在回過頭去看看)。
與認證管理器相似,訪問決策管理器也不是由本身來實現訪問控制的,而是經過一組投票者來投票決定(經過調用投票者的vote方法),訪問決策管理器統計投票結果並最終完成決策工做。下表列出了系統提供的3個訪問決策管理器的實現:
訪問決策管理器 |
如 何 決 策 |
AffirmativeBased |
當至少有一個投票者投允許訪問票時允許訪問 |
ConsensusBased |
當全部投票者都投允許訪問票時允許訪問 |
UnanimousBased |
當沒有投票者投拒絕訪問票時允許訪問 |
decisionVoters屬性爲訪問決策管理器定義了一組進行投票工做的投票者,那麼這些投票者是如何進行投票的呢?這就須要提org.springframework.security.vote.AccessDecisionVoter接口,全部的投票者都實現了這個接口並實現了其中的vote方法。該接口中還定義了3個int類型的常量:
int ACCESS_GRANTED = 1;(投同意票)
int ACCESS_ABSTAIN = 0;(投棄權票)
int ACCESS_DENIED = -1; (投反對票)
每一個決策投票者都返回這3個常量中一個,這取決與用戶是否有權限訪問當前請求的資源,訪問決策管理器再對這些投票結果進行統計。認證投票者的配置如上面所示。
loggerListener是一個可選項,它和咱們前面配置的Bean或者過濾器沒有關係,只是監聽系統的一些事件(
實現了ApplicationListener監聽接口),被它監聽的事件包括AuthenticationCredentialsNotFoundEvent事 件,AuthorizationFailureEvent事件,AuthorizedEvent事件,PublicInvocationEvent事件,相信你從他們 的名字就能看出來是一些什麼樣的事件,除非你的e文比我還差勁。loggerListener配置以下:
到此,本例所涉及到的全部配置都介紹完了,在下一篇中會介紹方法安全攔截器,以及如何使用它來保護咱們的方法調用,以及前面提到過的會在下一篇中介紹的,這裏不在一一列出。
接下來就是JSP頁面了,首先是login.jsp:
2 登陸失敗,請重試。錯誤緣由:<br/>
3 <font color="red">
4 <c:if test="${not empty SPRING_SECURITY_LAST_EXCEPTION}">
5 <c:out value="${SPRING_SECURITY_LAST_EXCEPTION}"></c:out>
6 </c:if>
7 </font>
8 </c:if>
9 <form action="<c:url value="/j_spring_security_check"/>" method="post">
10 <table>
11 <tr>
12 <td><label for="username">username:</label></td>
13 <td><input type="text" id="username" name="j_username"
value="<c:out value="${SPRING_SECURITY_LAST_USERNAME}"/>"/></td>
14 </tr>
15 <tr>
16 <td><label for="password">password:</label></td>
17 <td><input type="password" id="password" name="j_password" value=""/></td>
18 </tr>
19 <tr><td></td>
20 <td><input type="checkbox" name="_spring_security_remember_me">兩週內記住我</td>
21 </tr> 22 <tr><td colspan="2"><input type="submit" value="提交"/> 23 <input type="reset" value="重置"/></td></tr> 24 </table> 25 </form>
若是你有看源代碼,上面的某些參數,以及本文全部說起的東西你都不該該感到陌生。其它頁面也不在列出了,還有就是如何讓它運行起來,這些我相信你都能本身搞定。
附件1:springsecurity.rar(不包括JAR包)
補上使用命名空間配置實現的代碼,命名空間的詳細資料請參考Spring Security中文參考文檔,翻譯得很好,這裏就不在累述了,配置文件中也有比較詳細的註釋。另外例子中還包括了自定義UserDetailService的實現已經如何Ehcache緩存用戶信息,詳細的信息將在下一篇中講述。
附件2:springsecurity-namespace.rar(包括部分JAR包)