Spring Security學(xué)習(xí)總結(jié)二
背景知識(shí):Spring Security 學(xué)習(xí)總結(jié)一
SpringSide(你可以去官網(wǎng)了解更多信息,我也是從這里開始了解和學(xué)習(xí)Spring Security的)
前一篇文章里介紹了Spring Security的一些基礎(chǔ)知識(shí),相信你對(duì)Spring Security的工作流程已經(jīng)有了一定的了解,如果你同時(shí)在讀源代碼,那你應(yīng)該可以認(rèn)識(shí)的更深刻。在這篇文章里,我們將對(duì)Spring Security進(jìn)行一些自定義的擴(kuò)展,比如自定義實(shí)現(xiàn)UserDetailsService,保護(hù)業(yè)務(wù)方法以及如何對(duì)用戶權(quán)限等信息進(jìn)行動(dòng)態(tài)的配置管理。
說(shuō)明:
如果你通過(guò)Google搜索,可以找到很
多類似主題的文章,本文的目的在于通過(guò)這些實(shí)例來(lái)介紹的工作
原理,我覺得這些才是最重要的。相信你在讀完本文之后應(yīng)該可以按照自己的想法去擴(kuò)展Spring Secu
rity,這也是我寫這兩篇文章的目的。希望能對(duì)
初學(xué)者們有所幫助。
|
|
廢話少說(shuō),咱們直接進(jìn)入正題。
一 自定義UserDetailsService實(shí)現(xiàn)
UserDetailsService接口,這個(gè)接口中只定義了唯一的UserDetails loadUserByUsername(String username)方法,它通過(guò)用戶名來(lái)獲取整個(gè)UserDetails對(duì)象。
前一篇文章已經(jīng)介紹了系統(tǒng)提供的默認(rèn)實(shí)現(xiàn)方式InMemoryDaoImpl,它從配置文件中讀取用戶的身份信息(用戶名,密碼等),如果你的客戶想修改用戶信息,就需要直接修改配置文件(你需要告訴用戶配置文件的路徑,應(yīng)該在什么地方修改,如何把明文密碼通過(guò)MD5加密以及如何重啟服務(wù)器等)。聽起來(lái)是不是很費(fèi)勁啊!
在實(shí)際應(yīng)用中,我們可能需要提供動(dòng)態(tài)的方式來(lái)獲取用戶身份信息,最常用的莫過(guò)于數(shù)據(jù)庫(kù)了,當(dāng)然也可以是LDAP服務(wù)器等。本文首先介紹系統(tǒng)提供的一個(gè)默認(rèn)實(shí)現(xiàn)類JdbcDaoImpl(org.springframework.security.userdetails.jdbc. JdbcDaoImpl),它通過(guò)用戶名從數(shù)據(jù)庫(kù)中獲取用戶身份信息,修改配置文件,將userDetailsService Bean的配置修改如下:
1 <bean id="userDetailsService"
2 class="org.springframework.security.userdetails.jdbc.JdbcDaoImpl"
3 p:dataSource-ref="dataSource"
4 p:usersByUsernameQuery="select userName, passWord, enabled, from users where userName=?"
5 p:authoritiesByUsernameQuery="select
6 u.userName,r.roleName from users u,roles
7 r,users_roles ur where u.userId=ur.userId and
8 r.roleId=ur.roleId and u.userName=?"/>
JdbcDaoImpl類繼承自Spring Framework的JdbcDaoSupport類并實(shí)現(xiàn)了UserDetailsService接口,因?yàn)閺臄?shù)據(jù)庫(kù)中讀取信息,所以首先需要一個(gè)數(shù)據(jù)源對(duì)象,這里不在多說(shuō),這里需要重點(diǎn)介紹的是usersByUsernameQuery和authoritiesByUsernameQuery,屬性,它們的值都是一條SQL語(yǔ)句,JdbcDaoImpl類通過(guò)SQL從數(shù)據(jù)庫(kù)中檢索相應(yīng)的信息,usersByUsernameQuery屬性定義了通過(guò)用戶名檢索用戶信息的SQL語(yǔ)句,包括用戶名,密碼以及用戶是否可用,authoritiesByUsernameQuery屬性定義了通過(guò)用戶名檢索用戶權(quán)限信息的SQL語(yǔ)句,這兩個(gè)屬性都引用一個(gè)MappingSqlQuery(請(qǐng)參考Spring Framework相關(guān)資料)實(shí)例,MappingSqlQuery的mapRow()方法將一個(gè)ResultSet(結(jié)果集)中的字段映射為一個(gè)領(lǐng)域對(duì)象,Spring Security為我們提供了默認(rèn)的數(shù)據(jù)庫(kù)表,如下圖所示(摘自《Spring in
Action》):
圖1 JdbcDaoImp數(shù)據(jù)庫(kù)表
如果我們需要獲取用戶的其它信息就需要自己來(lái)擴(kuò)展系統(tǒng)的默認(rèn)實(shí)現(xiàn),首先應(yīng)該了解一下UserDetailsService實(shí)現(xiàn)的原理,還是要回到源代碼,以下是JdbcDaoImpl類的部分代碼:
1 private class UsersByUsernameMapping extends MappingSqlQuery {
2
3 protected UsersByUsernameMapping(DataSource ds) {
4
5 super(ds, usersByUsernameQuery);
6
7 declareParameter(new SqlParameter(Types.VARCHAR));
8
9 compile();
10
11 }
12
13 protected Object mapRow(ResultSet rs, int rownum) throws SQLException {
14
15 String username = rs.getString(1);
16
17 String password = rs.getString(2);
18
19 boolean enabled = rs.getBoolean(3);
20
21 UserDetails user = new User(username, password, enabled, true,
22
23 true, true, new GrantedAuthority[] {new GrantedAuthorityImpl("HOLDER")});
24
25 return user;
26 }
27
28 }
也許你已經(jīng)看出什么來(lái)了,對(duì)了,系統(tǒng)返回的UserDetails對(duì)象就是從這里來(lái)的,這就是讀源代碼的好處,DaoAuthenticationProvider提供者通過(guò)調(diào)用自己的authenticate(Authentication authentication)方法將用戶在登錄頁(yè)面輸入的用戶信息與這里從數(shù)據(jù)庫(kù)獲取的用戶信息進(jìn)行匹配,如果匹配成功則將用戶的權(quán)限信息賦給Authentication對(duì)象并將其存放在SecurityContext中,供其它請(qǐng)求使用。
那么我們要擴(kuò)展獲得更多的用戶信息,就要從這里下手了(數(shù)據(jù)庫(kù)表這里不在列出來(lái),可以參考項(xiàng)目的WebRoot/db目錄下的schema.sql文件)。比如我們自己的數(shù)據(jù)庫(kù)設(shè)計(jì)中是通過(guò)一個(gè)loginId和用戶名來(lái)登錄或者我們需要額外ID,EMAIL地址等信息,MySecurityJdbcDaoImpl實(shí)現(xiàn)如下:
1 protectedclass UsersByUsernameMapping extends MappingSqlQuery {
2
3 protected UsersByUsernameMapping(DataSource ds) {
4
5 super(ds, usersByUsernameQuery);
6
7 declareParameter(new SqlParameter(Types.VARCHAR));
8
9 compile();
10
11 }
12
13 protected Object mapRow(ResultSet rs, int rownum) throws SQLException {
14
15 // TODO Auto-generated method stub
16
17 String userName = rs.getString(1);
18
19 String passWord = rs.getString(2);
20
21 boolean enabled = rs.getBoolean(3);
22
23 Integer userId = rs.getInt(4);
24
25 String email = rs.getString(5);
26
27 MyUserDetails user = new MyUser(userName, passWord, enabled, true,
28 true,true, new GrantedAuthority[]{new
29 GrantedAuthorityImpl("HOLDER")});
30
31 user.setEmail(email);
32
33 user.setUserId(userId);
34
35 return user;
36
37 }
38
39 }
如果你已經(jīng)看過(guò)源代碼,你會(huì)發(fā)現(xiàn)這里只是其中的一部分代碼 ,具體的實(shí)現(xiàn)請(qǐng)看項(xiàng)目的MySecurityJdbcDaoImpl類實(shí)現(xiàn),以及MyUserDetails和MyUser類,這里步在一一列出。
如果使用Hibernate來(lái)操作數(shù)據(jù)庫(kù),你也可以從你的DAO中獲取用戶信息,最后你只要將存放了用戶身份信息和權(quán)限信息的列表(List)返回給系統(tǒng)就可以。
提示:
這里沒有介紹更多的細(xì)節(jié)問題,主要還是想你自己能通過(guò)讀源代碼來(lái)加深理解,本人水平也有限,
相信你從源代碼中能悟出更多的有價(jià)值的東西。
|
|
每當(dāng)用戶請(qǐng)求一個(gè)受保護(hù)的資源時(shí),就會(huì)調(diào)用認(rèn)證管理器以獲取用戶認(rèn)證信息,但是如果我們的用戶信息保存在數(shù)據(jù)庫(kù)中,那么每次請(qǐng)求都從數(shù)據(jù)庫(kù)中獲取信息將會(huì)影響系統(tǒng)性能,那么將用戶信息進(jìn)行緩存就有必要了,下面就介紹如何在Spring Security中使用緩存。
二緩存用戶信息
查看AuthenticationProvider接口的實(shí)現(xiàn)類AbstractUserDetailsAuthenticationProvider抽象類(我們配置文件中配置的DaoAuthenticationProvider類繼承了該類)的源代碼,會(huì)有一行代碼:
1 UserDetails user = this.userCache.getUserFromCache(username);
DaoAuthenticationProvider認(rèn)證提供者使用UserCache接口的實(shí)現(xiàn)來(lái)實(shí)現(xiàn)對(duì)用戶信息的緩存,修改DaoAuthenticationProvider的配置如下:
1 <bean id="daoAuthenticationProvider"
2 class="org.springframework.security.providers.dao.DaoAuthenticationProvider"
3 p:userCache-ref="userCache"
4 p:passwordEncoder-ref="passwordEncoder"
5 p:userDetailsService- ref="userDetailsService"/>
這里我們加入了對(duì)userCache Bean的引用,userCache使用Ehcache來(lái)實(shí)現(xiàn)對(duì)用戶信息的緩存。userCache配置如下:
1 <bean id="userCache"
2 class="org.springframework.security.providers.dao.cache.EhCacheBasedUserCache"
3 p:cache-ref="cache"/>
4 <bean id="cache"
5 class="org.springframework.cache.ehcache.EhCacheFactoryBean"
6 p:cacheManager-ref="cacheManager"
7 p:cacheName="userCache"/>
8 <bean id="cacheManager"
9 class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"
10 p:configLocation="classpath:ehcache.xml">
11 </bean>
我們這里使用的是EhCacheBasedUserCache,也就是用EhCache實(shí)現(xiàn)緩存的,另外系統(tǒng)還提供了一個(gè)默認(rèn)的實(shí)現(xiàn)類NullUserCache類,我們可以通過(guò)源代碼了解到,無(wú)論上面使用這個(gè)類都返回一個(gè)null值,也就是不使用緩存。
三保護(hù)業(yè)務(wù)方法
從第一篇文章中我們已經(jīng)了解到,Spring Security使用Servlet過(guò)濾器來(lái)攔截用戶的請(qǐng)求來(lái)保護(hù)WEB資源,而這里卻是使用Spring 框架的AOP來(lái)提供對(duì)方法的聲明式保護(hù)。它通過(guò)一個(gè)攔截器來(lái)攔截方法調(diào)用,并調(diào)用方法安全攔截器來(lái)保護(hù)方法。
在介紹之前,我們先回憶一下過(guò)濾器安全攔截器是如何工作的。過(guò)濾器安全攔截器首先調(diào)用AuthenticationManager認(rèn)證管理器認(rèn)證用戶信息,如果用過(guò)認(rèn)證則調(diào)用AccessDecisionManager訪問決策管理器來(lái)驗(yàn)證用戶是否有權(quán)限訪問objectDefinitionSource中配置的受保護(hù)資源。
首先看看如何配置方法安全攔截器,它和過(guò)濾器安全攔截器一方繼承自AbstractSecurityInterceptor抽象類(請(qǐng)看源代碼),如下:
1 <bean id="methodSecurityInterceptor"
2 class="org.springframework.security.intercept.method.aopalliance.MethodSecurityInterceptor"
3 p:authenticationManager-ref="authenticationManager"
4 p:accessDecisionManager-ref="accessDecisionManager">
5 <property name="objectDefinitionSource">
6 <value>
com.test.service.UserService.get*=ROLE_SUPERVISOR
7 </value>
8 </property>
9 </bean>
這段代碼是不是很眼熟啊,哈哈~,這和我們配置的過(guò)濾器安全攔截器幾乎完全一樣,方法安全攔截器的處理過(guò)程實(shí)際和過(guò)濾器安全攔截器的實(shí)現(xiàn)機(jī)制是相同的,這里就在累述,詳細(xì)介紹請(qǐng)參考< Spring Security 學(xué)習(xí)總結(jié)一>中相關(guān)部分。但是也有不同的地方,那就是這里的objectDefinitionSource的配置,在等號(hào)前面的不在是URL資源,而是需要保護(hù)的業(yè)務(wù)方法,等號(hào)后面還是訪問該方法需要的用戶權(quán)限。我們這里配置的com.test.service.UserService.get*表示對(duì)com.test.service包下UserService類的所有以get開頭的方法都需要ROLE_SUPERVISOR權(quán)限才能調(diào)用。這里使用了提供的實(shí)現(xiàn)方法MethodSecurityInterceptor,系統(tǒng)還給我們提供了aspectj的實(shí)現(xiàn)方式,這里不在介紹(我也正在學(xué)…),讀者可以參考其它相關(guān)資料。
之前已經(jīng)提到過(guò)了,Spring Security使用Spring 框架的AOP來(lái)提供對(duì)方法的聲明式保護(hù),即攔截方法調(diào)用,那么接下來(lái)就是創(chuàng)建一個(gè)攔截器,配置如下:
1 <bean id="autoProxyCreator"
2 class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
3 <property name="interceptorNames">
4 <list>
5 <value>methodSecurityInterceptor</value>
6 </list>
7 </property>
8 <property name="beanNames">
9 <list>
10 <value>userService</value>
11 </list>
12 </property>
13 </bean>
userService是我們?cè)赼pplicationContext.xml中配置的一個(gè)Bean,AOP的知識(shí)不是本文介紹的內(nèi)容。到這里保護(hù)業(yè)務(wù)方法的配置就介紹完了。
四將資源放在數(shù)據(jù)庫(kù)中
現(xiàn)在,你的用戶提出了新的需求,它們需要自己可以給系統(tǒng)用戶分配或者取消權(quán)限。其實(shí)這個(gè)并不是什么新鮮事,作為開發(fā)者,你也應(yīng)該為用戶提供這樣的功能。那么我們就需要這些受保護(hù)的資源和用戶權(quán)限等信息都是動(dòng)態(tài)的,你可以選擇把它們存放在數(shù)據(jù)庫(kù)中或者LDAP服務(wù)器上,本文以數(shù)據(jù)庫(kù)為例,介紹如何實(shí)現(xiàn)用戶權(quán)限的動(dòng)態(tài)控制。
通過(guò)前面的介紹,你可能也注意到了,不管是MethodSecurityInterceptor還是FilterSecurityInterceptor都使用authenticationManager和accessDecisionManager屬性用于驗(yàn)證用戶,并且都是通過(guò)使用objectDefinitionSource屬性來(lái)定義受保護(hù)的資源。不同的是過(guò)濾器安全攔截器將URL資源與權(quán)限關(guān)聯(lián),而方法安全攔截器將業(yè)務(wù)方法與權(quán)限關(guān)聯(lián)。
你猜對(duì)了,我們要做的就是自定義這個(gè)objectDefinitionSource的實(shí)現(xiàn),首先讓我們來(lái)認(rèn)識(shí)一下系統(tǒng)為我們提供的ObjectDefinitionSource接口,objectDefinitionSource屬性正是指向此接口的實(shí)現(xiàn)類。該接口中定義了3個(gè)方法,ConfigAttributeDefinition getAttributes(Object
object)方法用戶獲取保護(hù)資源對(duì)應(yīng)的權(quán)限信息,該方法返回一個(gè)ConfigAttributeDefinition對(duì)象(位于org.springframework.security包下),通過(guò)源代碼我們可以知道,該對(duì)象中實(shí)際就只有一個(gè)List列表,我們可以通過(guò)使用ConfigAttributeDefinition類的構(gòu)造函數(shù)來(lái)創(chuàng)建這個(gè)List列表,這樣,安全攔截器就通過(guò)調(diào)用getAttributes(Object
object)方法來(lái)獲取ConfigAttributeDefinition對(duì)象,并將該對(duì)象和當(dāng)前用戶擁有的Authentication對(duì)象傳遞給accessDecisionManager(訪問決策管理器,請(qǐng)查看org.springframework.security.vote包下的AffirmativeBased類,該類是訪問決策管理器的一個(gè)實(shí)現(xiàn)類,它通過(guò)一組投票者來(lái)決定用戶是否有訪問當(dāng)前請(qǐng)求資源的權(quán)限),訪問決策管理器在將其傳遞給AffirmativeBased類維護(hù)的投票者,這些投票者從ConfigAttributeDefinition對(duì)象中獲取這個(gè)存放了訪問保護(hù)資源需要的權(quán)限信息的列表,然后遍歷這個(gè)列表并與Authentication對(duì)象中GrantedAuthority[]數(shù)據(jù)中的用戶權(quán)限信息進(jìn)行匹配,如果匹配成功,投票者就會(huì)投贊成票,否則就投反對(duì)票,最后訪問決策管理器來(lái)統(tǒng)計(jì)這些投票決定用戶是否能訪問該資源。是不是又覺得亂了,還是那句話,如果你結(jié)合源代碼你現(xiàn)在一定更明白了。
說(shuō)了這么些,那我們到底應(yīng)該如何來(lái)實(shí)現(xiàn)這個(gè)ObjectDefinitionSource接口呢?
首先還是說(shuō)說(shuō)Acegi Seucrity 1.x版本,org.acegisecurity.intercept.web和org.acegisecurity.intercept.method包下AbstractFilterInvocationDefinitionSource和AbstractMethodDefinitionSource兩個(gè)抽象類,這兩個(gè)類分別實(shí)現(xiàn)了FilterInvocationDefinitionSource和MethodDefinitionSource接口,而這兩個(gè)接口都繼承自O(shè)bjectDefinitionSource接口并實(shí)現(xiàn)了其中的方法。兩個(gè)抽象類都使用方法模板模式來(lái)實(shí)現(xiàn),將具體的實(shí)現(xiàn)方法交給了子類。
提示:
兩個(gè)抽象類實(shí)現(xiàn)了各自接口的
getAttributes(Object object)方法并在此方法中調(diào)用lookupAttributes
(Method method)方法,而實(shí)際該方法在抽象類中并沒有具體的實(shí)現(xiàn),而是留給了子類去實(shí)現(xiàn)。
|
|
在Acegi Seucrity 1.x版本中,系統(tǒng)為我們提供了默認(rèn)的實(shí)現(xiàn),MethodDefinitionMap類用于返回方法的權(quán)限信息,而PathBasedFilterInvocationDefinitionMap類和RegExpBasedFilterInvocationDefinitionMap類用于返回URL資源對(duì)應(yīng)的權(quán)限信息,也就是ConfigAttributeDefinition對(duì)象,現(xiàn)在也許明白一點(diǎn)兒了吧,我們只要按照這三個(gè)類的實(shí)現(xiàn)方式(也就是”模仿”,從后面的代碼中你可以看到)從數(shù)據(jù)庫(kù)中獲取用戶信息和權(quán)限信息然后封裝成一個(gè)ConfigAttributeDefinition對(duì)象返回即可(其實(shí)就是一個(gè)List列表,前面已經(jīng)介紹過(guò)了),相信通過(guò)Hibernate從數(shù)據(jù)庫(kù)中獲取一個(gè)列表應(yīng)該是再容易不過(guò)的了。
回到Spring Security,系統(tǒng)為我們提供的默認(rèn)實(shí)現(xiàn)有些變化,DefaultFilterInvocationDefinitionSource和DelegatingMethodDefinitionSource兩個(gè)類,從名字也可以看出來(lái)它們分別是干什么的了。這兩個(gè)類分別實(shí)現(xiàn)了FilterInvocationDefinitionSource和MethodDefinitionSource接口,而這兩個(gè)接口都繼承自O(shè)bjectDefinitionSource接口并實(shí)現(xiàn)了其中的方法,這和1.x版本中一樣。它們都是從配置文件中得到資源和相應(yīng)權(quán)限的信息。
通過(guò)上面的介紹,你或許更名白了一些,那我們下面要做的就是實(shí)現(xiàn)系統(tǒng)的FilterInvocationDefinitionSource和MethodDefinitionSource接口,只是數(shù)據(jù)源不是從配置文件中讀取配置信息是數(shù)據(jù)庫(kù)而已。
我們這里對(duì)比著Acegi
Seucrity 1.x版本中的實(shí)現(xiàn),我個(gè)人認(rèn)為它更好理解,還是請(qǐng)你好好看看源代碼。
1 自定義FilterInvocationDefinitionSource
在2.0中,系統(tǒng)沒有在系統(tǒng)抽象類,所以我們還是使用1.x中的實(shí)現(xiàn)方式,首先通過(guò)一個(gè)抽象類來(lái)實(shí)現(xiàn)ObjectDefinitionSource接口。代碼如下:
1 public ConfigAttributeDefinition getAttributes(Object object)
2
3 throws IllegalArgumentException {
4
5 if (object == null || !(this.supports(object.getClass()))) {
6
7 thrownew IllegalArgumentException("Object must be a FilterInvocation");
8
9 }
10
11 String url = ((FilterInvocation)object).getRequestUrl();
12
13 returnthis.lookupAttributes(url);
14
15 }
16
17 publicabstract ConfigAttributeDefinition lookupAttributes(String url);
18
19 @SuppressWarnings("unchecked")
20
21 publicabstract Collection getConfigAttributeDefinitions();
22
23 @SuppressWarnings("unchecked")
24
25 publicboolean supports(Class clazz) {
26
27 return FilterInvocation.class.isAssignableFrom(clazz);
28
29 }
這段代碼你也可以在1.0中找到,getAttributes方法的入口參數(shù)是一個(gè)Object對(duì)象,這是由系統(tǒng)傳給我們的,因?yàn)槭荱RL資源的請(qǐng)求,所有可以將這個(gè)Object對(duì)象強(qiáng)制轉(zhuǎn)換為FilterInvocation對(duì)象,并通過(guò)調(diào)用它的getRequestUrl()方法來(lái)獲取用戶當(dāng)前請(qǐng)求的URL地址,然后調(diào)用子類需要實(shí)現(xiàn)的lookupAttributes方法并將該URL地址作為參數(shù)傳給該方法,下面是具體的實(shí)現(xiàn)類DataBaseFilterInvocationDefinitionSource類的代碼,也就是我們需要實(shí)現(xiàn)抽象父類的lookupAttributes方法:
1 @Override
2
3 public ConfigAttributeDefinition lookupAttributes(String url) {
4
5 // TODO Auto-generated method stub
6
7 //初始化數(shù)據(jù),從數(shù)據(jù)庫(kù)讀取
8
9 cacheManager.initResourceInCache();
10
11 if (isUseAntPath()) {
12
13 int firstQuestionMarkIndex = url.lastIndexOf("?");
14
15 if (firstQuestionMarkIndex != -1) {
16
17 url = url.substring(0, firstQuestionMarkIndex);
18
19 }
20
21 }
22
23 //將URL在比較前都轉(zhuǎn)換為小寫
24
25 if (isConvertUrlToLowercaseBeforeComprison()) {
26
27 url = url.toLowerCase();
28
29 }
30
31 //獲取所有的URL
32
33 List<String> urls = cacheManager.getUrlResources();
34
35 //倒敘排序--如果不進(jìn)行排序,如果用戶使用瀏覽器的導(dǎo)航工具訪問頁(yè)面可能出現(xiàn)問題
36
37 //例如:訪問被拒絕后用戶刷新頁(yè)面
38
39 Collections.sort(urls);
40
41 Collections.reverse(urls);
42
43 GrantedAuthority[] authorities = new GrantedAuthority[0];
44
45 //將請(qǐng)求的URL與配置的URL資源進(jìn)行匹配,并將正確匹配的URL資源對(duì)應(yīng)的權(quán)限
46
47 //取出
48
49 for (String resourceName_url : urls) {
50
51 boolean matched = false;
52
53 //使用ant匹配URL
54
55 if (isUseAntPath()) {
56
57 matched = pathMatcher.match(resourceName_url, url);
58
59 } else {//perl5編譯URL
60
61 Pattern compliedPattern = null;
62
63 Perl5Compiler compiler = new Perl5Compiler();
64
65 try {
66
67 compliedPattern = compiler.compile(resourceName_url, Perl5Compiler.READ_ONLY_MASK);
68
69 } catch (MalformedPatternException e) {
70
71 e.printStackTrace();
72
73 }
74
75 matched = matcher.matches(url, compliedPattern);
76
77 }
78
79 //匹配正確,獲取響應(yīng)權(quán)限
80
81 if (matched) {
82
83 //獲取正確匹配URL資源對(duì)應(yīng)的權(quán)限
84
85 ResourcDetail detail = cacheManager.getResourcDetailFromCache(resourceName_url);
86
87 authorities = detail.getAuthorities();
88
89 break;
90
91 }
92
93 }
94
95 //將權(quán)限封裝成ConfigAttributeDefinition對(duì)象返回(使用ConfigAttributeEditor)
96
97 if (authorities.length > 0) {
98
99 String authTemp = "";
100
101 for (GrantedAuthority grantedAuthority : authorities) {
102
103 authTemp += grantedAuthority.getAuthority() + ",";
104
105 }
106
107 String authority = authTemp.substring(0, (authTemp.length() - 1));
108
109 System.out.println(authority);
110
111 ConfigAttributeEditor attributeEditor = new ConfigAttributeEditor();
112
113 attributeEditor.setAsText(authority.trim());
114
115 return (ConfigAttributeDefinition)attributeEditor.getValue();
116
117 }
118
119 returnnull;
120
121 }
我們這里同樣使用了緩存,它參考自系統(tǒng)的UseCache接口的實(shí)現(xiàn),這里不在介紹,你可以查看本例的源代碼和系統(tǒng)的實(shí)現(xiàn)和本例的配置文件。這里將用戶請(qǐng)求的URL地址與從數(shù)據(jù)庫(kù)中獲取的受保護(hù)的URL資源使用ant和perl5匹配(這取決與你的配置),如果匹配成功則從緩存中獲取訪問該資源需要的權(quán)限信息,并將其封裝成ConfigAttributeDefinition對(duì)象返回,這里使用org.springframework.security.ConfigAttributeEditor類,該類提供了一個(gè)setAsText(String s),該方法收取一個(gè)字符串作為參數(shù),在該方法中創(chuàng)建ConfigAttributeDefinition對(duì)象并將字符串參數(shù)傳遞給ConfigAttributeDefinition類的構(gòu)造函數(shù)來(lái)初始化該對(duì)象。詳細(xì)的實(shí)現(xiàn)還是請(qǐng)你看源代碼。現(xiàn)在我們?cè)谂渲梦募砑幼约旱膶?shí)現(xiàn),如下:
1 <bean id="objectDefinitionSource"
2 class="org.security.intercept.web.DataBaseFilterInvocationDefinitionSource"
3 p:convertUrlToLowercaseBeforeComprison="true"
4 p:useAntPath="true"
5 p:cacheManager-ref="securityCacheManager"/>
convertUrlToLowercaseBeforeComprison屬性定義了在匹配之前將URL都轉(zhuǎn)換為小寫,useAntPath屬性定義使用Ant方式匹配URL,cacheManager屬性定義了指向另一個(gè)Bean的引用,我們使用它從緩存中獲取相應(yīng)的信息。
2
自定義MethodDefinitionSource
將方法資源存放在數(shù)據(jù)庫(kù)中的實(shí)現(xiàn)與URL資源類似,這里不在累述,下面是DataBaseMethodInvocationDefinitionSource的源代碼,讀者可以參考注釋進(jìn)行閱讀(該類也是繼承自一個(gè)自定義的抽象類AbstractMethodDefinitionSource):
1 public ConfigAttributeDefinition lookupAttributes(Method method, Class targetClass) {
2
3 // TODO Auto-generated method stub
4
5 //初始化資源并緩存
6
7 securityCacheManager.initResourceInCache();
8
9 //獲取所有方法資源
10
11 List<String> methods = securityCacheManager.getMethodResources();
12
13 //權(quán)限集合
14
15 Set<GrantedAuthority> authSet = new HashSet<GrantedAuthority>();
16
17 //遍歷方法資源,并獲取匹配的資源名稱,然后從緩存中獲取匹配正確
18
19 //的資源對(duì)應(yīng)的權(quán)限(ResourcDetail對(duì)象的GrantedAuthority[]對(duì)象數(shù)據(jù))
20
21 for (String resourceName_method : methods) {
22
23 if (isMatch(targetClass, method, resourceName_method)) {
24
25 ResourcDetail detail = securityCacheManager.getResourcDetailFromCache(resourceName_method);
26
27 if (detail == null) {
28
29 break;
30
31 }
32
33 GrantedAuthority[] authorities = detail.getAuthorities();
34
35 if (authorities == null || authorities.length == 0) {
36
37 break;
38
39 }
40
41 authSet.addAll(Arrays.asList(authorities));
42
43 }
44
45 }
46
47 if (authSet.size() > 0) {
48
49 String authString = "";
50
51 for (GrantedAuthority grantedAuthority : authSet) {
52
53 authString += grantedAuthority.getAuthority() + ",";
54
55 }
56
57 String authority = authString.substring(0, (authString.length() - 1));
58
59 System.out.println(">>>>>>>>>>>>>>>" + authority);
60
61 ConfigAttributeEditor attributeEditor = new ConfigAttributeEditor();
62
63 attributeEditor.setAsText(authority.trim());
64
65 return (ConfigAttributeDefinition)attributeEditor.getValue();
66
67 }
68
69 returnnull;
70
71 }
isMatch方法用于對(duì)用戶當(dāng)前調(diào)用的方法與受保護(hù)的方法進(jìn)行匹配,與URL資源類似,請(qǐng)參考代碼。下面是applicationContext-security.xml文件中的配置,請(qǐng)查看該配置文件。
1 <bean id="methodDefinitionSource"
2 class="org.security.intercept.method.DataBaseMethodInvocationDefinitionSource"
3 p:securityCacheManager-ref="securityCacheManager"/>
securityCacheManager屬性定義了指向另一個(gè)Bean的引用,我們使用它從緩存中獲取相應(yīng)的信息。這個(gè)Bean和前一節(jié)中介紹的一樣。只是這里我們獲取的是方法保護(hù)定義資源。
本文到此也結(jié)束了,還請(qǐng)各位多指教。
附件:springsecurity2.rar