1 <bean id="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:
1 <bean id="rememberMeServices"
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所定義相同的請求時它開始工作:
1 <bean id="authenticationProcessingFilter"
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.認證管理其配置如下:
1 <bean id="authenticationManager"
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,它從數據庫中讀取用戶信息驗證身份,配置如下:
1 <bean id="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,從而可以獲取用戶名,密碼以及
權限等信息。代碼如下:
1 UserDetails details = (UserDetails)authentication.getPrincipal();
2
3 GrantedAuthority[] authority = details.getAuthorities();
前面介紹了DaoAuthenticationProvider,它可以從數據庫中讀取用戶信息,同樣也可以從一個用戶屬性文件中讀取,下一篇文章中我們
在介紹如何從數據庫中讀取用戶信息,當然還會涉及到更深入的東西,比如根據自己系統的需要自定義UserDetails和
UserDetailsService,這個只是讓你對整個系統有個簡單的了解,所以我們使用用戶屬性文件(users.properties)來存儲用
戶信息:
1 admin=admin,ROLE_SUPERVISOR
2
3 user1=user1,ROLE_USER
4
5 user2=user2,ROLE_USER
6
7 user3=user3,disabled,ROLE_USER
配置userDetailsService:
1 <bean id="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字符串的每一行都用鍵值對的形式表示,前面是用戶名,然后是等號,后面是賦予該用戶的密碼/權限等信息,它們使用逗號隔開。比如:
1 admin=admin,ROLE_SUPERVISOR
定義了一個名為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的配置:
1 <bean id="concurrentSessionController"
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的生命周期事件,配置如下:
1 <listener>
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:
1 <bean id="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過濾器配置:
1 <bean id="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:
1 <bean id="exceptionTranslationFilter"
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"/>