接上一篇 總結(jié)Spring Security之 關(guān)于Authentication
* 關(guān)于授權(quán)
AcessDecisionManager是管授權(quán)的。具體授權(quán)(authorization)的工作是交給一系列Voter來(lái)做的。每個(gè)Voter都實(shí)現(xiàn)AccessDecisionVoter接口的vote方法,返回
int ACCESS_GRANTED = 1;(投贊成票)
int ACCESS_ABSTAIN = 0;(投棄權(quán)票)
int ACCESS_DENIED = -1; (投反對(duì)票)
AccessDecisioinManager有三種實(shí)現(xiàn):
AffirmativeBased -當(dāng)至少有一個(gè)投票者投允許訪(fǎng)問(wèn)票時(shí)允許訪(fǎng)問(wèn)
ConsensusBased - 當(dāng)所有投票者都投允許訪(fǎng)問(wèn)票時(shí)允許訪(fǎng)問(wèn)
UnanimousBased - 當(dāng)沒(méi)有投票者投拒絕訪(fǎng)問(wèn)票時(shí)允許訪(fǎng)問(wèn)
Spring Security提供了一個(gè)實(shí)用的voter:
RoleVoter participates in a vote when the secured resource has a configuration attribute whose name starts with ROLE_.
* 關(guān)于保護(hù)web
Spring Security提供一套filter chain保護(hù)web應(yīng)用
注意FilterToBeanProxy
<filter>
<filter-name>Spring Security Filter Chain Proxy</filter-name>
<filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class>
<init-param>
<param-name>targetClass</param-name>
<param-value>org.acegisecurity.util.FilterChainProxy</param-value>
</init-param>
</filter>
跑下題,說(shuō)FilterToBeanProxy的作用:filter配置在web.xml里,它是不可能有IOC的概念的,服務(wù)沒(méi)辦法自動(dòng)注射到filter中,這時(shí)候有一個(gè)辦法就是使用WebApplicationContextUtils(如WebApplicationContextUtils.getWebApplicationContext(servletContext).getBean("securityManager"))。按照SpringSide的說(shuō)法,WebApplicationContextUtils適合廚房,衛(wèi)生間,草坪,屋頂?shù)确浅R?guī)場(chǎng)所,汗…這么做的一個(gè)缺點(diǎn)是,Spring的API還是入侵到你的code里了(向來(lái)對(duì)Rod Johnson的入侵論不感冒,感覺(jué)這純是順手拽過(guò)來(lái)揍EJB的板磚,不值得深究,pojo粉絲估計(jì)要拍我板磚了,俺穿個(gè)軟猬甲先。入侵論深入人心之后,大家反而愿意對(duì)Spring的偶爾入侵指指點(diǎn)點(diǎn)。另外不喜歡Spring的兩個(gè)方面是,從DDD的角度看Spring不natural;contract first我覺(jué)得是扯淡的,或許在之后的博客亂噴一下我的看法)解決入侵的方法就是FilterToBeanProxy, 它把真正的工作代理給target class,而spring拿到target class的類(lèi)名后,就歸它管了。 proxy是個(gè)簡(jiǎn)單的模式,但用在這里感覺(jué)還是挺巧妙的。
回到正題,filter chain里至少包括四個(gè)filter:
Integration Filter - 拿之前的authentication, 通常是從session里拿。
AuthenticationProcessing Filter - 處理authentication request, 比如登錄的時(shí)候
Exception Translation Filter - 典型的處理是,把AuthenticationException轉(zhuǎn)登錄頁(yè),把AccessNeniedException轉(zhuǎn)403錯(cuò)誤頁(yè)
Filter Security Interceptor - 它是真正做權(quán)限處理的,把AuthenticaionManager 和AccessDecisionManager串起來(lái).所以FilterSecurityInterceptor是重點(diǎn)。
先看一個(gè)配置示例:
1: <beans:bean id="resourceSecurityInterceptor" class="org.springframework.security.intercept.web.FilterSecurityInterceptor">
2: <beans:property name="authenticationManager" ref="authenticationManager"/>
3: <beans:property name="accessDecisionManager" ref="accessDecisionManager"/>
4: <beans:property name="objectDefinitionSource" ref="secureResourceFilterInvocationDefinitionSource" />
5: <beans:property name="observeOncePerRequest" value="false" />
6: <custom-filter after="LAST" />
7: </beans:bean>
這里objectDefinitionSource屬性來(lái)定義受保護(hù)的資源,保護(hù)web的時(shí)候,“資源”代表url. 保護(hù)method的時(shí)候,資源就代表method, 關(guān)于保護(hù)method之后再討論。【有取舍地引用 spring security學(xué)習(xí)總結(jié)】首先讓我們來(lái)認(rèn)識(shí)一下系統(tǒng)為我們提供的 ObjectDefinitionSource接口,objectDefinitionSource屬性正是指向此接口的實(shí)現(xiàn)類(lèi)。其中有個(gè)重要方法,ConfigAttributeDefinition getAttributes(Object object)方法用戶(hù)獲取保護(hù)資源對(duì)應(yīng)的權(quán)限信息,該方法返回一個(gè)ConfigAttributeDefinition對(duì)象,該對(duì)象中實(shí)際就只有一個(gè)List列表,我們可以通過(guò)使用 ConfigAttributeDefinition類(lèi)的構(gòu)造函數(shù)來(lái)創(chuàng)建這個(gè)List列表,這樣,安全攔截器就通過(guò)調(diào)用 getAttributes(Object object)方法來(lái)獲取ConfigAttributeDefinition對(duì)象,并將該對(duì)象和當(dāng)前用戶(hù)擁有的Authentication對(duì)象傳遞給 accessDecisionManager,accessDecisionManager再將其傳遞給voter們,這些投票者從ConfigAttributeDefinition對(duì)象中獲取這個(gè)存放了訪(fǎng)問(wèn)保護(hù)資源需要的權(quán)限信息的列表,然后遍歷這個(gè)列表并與Authentication對(duì)象中GrantedAuthority[]數(shù)據(jù)中的用戶(hù)權(quán)限信息進(jìn)行匹配,如果匹配成功,投票者就會(huì)投贊成票,否則就投反對(duì)票,最后accessDecisionManager來(lái)統(tǒng)計(jì)這些投票決定用戶(hù)是否能訪(fǎng)問(wèn)該資源。【有取舍地引用 spring security學(xué)習(xí)總結(jié)結(jié)束】如果你用的是role voter的話(huà),那么返回的ConfigAttributeDefinition其實(shí)就是一系列Role_XXX
說(shuō)到這里小結(jié)一下之前說(shuō)的認(rèn)證,授權(quán)和"保護(hù)web”,涉及三個(gè)概念,user, role, resource. 認(rèn)證的過(guò)程是鑒定你的身份,并且順便把role也關(guān)聯(lián)上。user和role是多對(duì)多的。 授權(quán)是處理role和resource之間的關(guān)系的,也是多對(duì)多。典型的resource是servlet URL路徑。從實(shí)戰(zhàn)的角度講,authentication需要我們自己做的有兩個(gè)事情:一是通過(guò)實(shí)現(xiàn)UserService.loadUserByUsername(String userName)完成認(rèn)證的本職工作;二是通過(guò)實(shí)現(xiàn)Users.getAuthorities()把user和role關(guān)聯(lián)起來(lái)。
* 保護(hù)方法:
Spring提供了兩種方式保護(hù)方法,一種是AOP,另一種是annotation.
AOP:
如果你已經(jīng)看懂了上面關(guān)于objectDefinitionSource的介紹和小結(jié)部分,那么直接看配置應(yīng)該很容易可以看懂:
1:
2: <bean id="autoProxyCreator"
3: class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
4:
5: <property name="interceptorNames">
6:
7: <list>
8:
9: <value>securityInterceptor</value>
10:
11: </list>
12: </property>
13: <property
14: name="beanNames">
15:
16: <list>
17:
18: <value>courseService</value>
19:
20: <value>billingService</value>
21:
22: </list>
23: </property>
24: </bean>
25:
26: <bean id="securityInterceptor"
27:
28: class="org.acegisecurity.intercept.method.MethodSecurityInterceptor">
29:
30: <property name="authenticationManager">
31: <ref
32: bean="authenticationManager"/>
33: </property>
34:
35: <property name="accessDecisionManager">
36: <ref
37: bean="accessDecisionManager"/>
38: </property>
39:
40: <property name="objectDefinitionSource">
41:
42: <value>
43:
44: com.springinaction.springtraining.service.CourseService.createCourse=ROLE_ADMIN
45:
46: com.springinaction.springtraining.service.CourseService.enroll*=ROLE_ADMIN,ROLE_REGISTRAR
47:
48: </value>
49: </property>
50: </bean>
注意上面的倒數(shù)幾行定義了方法和對(duì)應(yīng)的role
annotation:
最終目標(biāo)是通過(guò)這種方式定義權(quán)限
1: /**
2: * @@org.acegisecurity.SecurityConfig("ROLE_ADMIN")
3: * @@org.acegisecurity.SecurityConfig("ROLE_REGISTRAR")
4: */
5: public void enrollStudentInCourse(Course course, Student student) throws CourseException;
這個(gè)看起來(lái)很酷,但是有個(gè)drawback是,如果你做的是產(chǎn)品,并且允許用戶(hù)靈活配置role和method(capability)的功能,那么annotation就不適用了,因?yàn)閍nnotation是寫(xiě)死在code里的,compile time已經(jīng)把role和method之間的map寫(xiě)死了。
這個(gè)沒(méi)啥理論邏輯可談,直接貼spring in action的配置:
1:
2: <bean
3: id="attributes"class="org.springframework.metadata.commons.CommonsAttributes"/>
4:
5: <bean id="objectDefinitionSource"
6: class="org.acegisecurity.intercept.method.MethodDefinitionAttributes">
7:
8: <property name="attributes"><ref
9: bean="attributes"/></property>
10: </bean>
11:
12: <bean id="securityInterceptor"
13:
14: class="org.acegisecurity.intercept.method.MethodSecurityInterceptor">
15: …
16:
17: <property name="objectDefinitionSource">
18: <ref
19: bean="objectDefinitionSource"/>
20:
21: </property>
22: </bean>