保護(hù)業(yè)務(wù)層
Spring Security支持添加授權(quán)層(或者基于授權(quán)的數(shù)據(jù)處理)到應(yīng)用中所有Spring管理的bean中。盡管很多的開(kāi)發(fā)人員關(guān)注層的安全蚓让,其實(shí)業(yè)務(wù)層的安全同等重要饭耳,因?yàn)閻阂獾挠脩艨赡軙?huì)穿透web層执解,能夠通過(guò)沒(méi)有UI的前端訪問(wèn)暴露的服務(wù),如使用webservice觅赊。
讓我們查看下面的圖以了解我們將要添加安全層的位置:
Spring Security有兩個(gè)主要技術(shù)以實(shí)現(xiàn)方法的安全:
- 事先授權(quán)(Pre-authorization)保證在執(zhí)行一個(gè)方法之前需要滿足特定的要求——例如吮螺,一個(gè)用戶要擁有特定的GrantedAuthority鸠补,如ROLE_ADMIN紫岩。不能滿足聲明的條件將會(huì)導(dǎo)致方法調(diào)用失敗勋陪;
- 事后授權(quán)(Post-authorization)保證在方法返回時(shí)硫兰,調(diào)用的安全實(shí)體滿足聲明的條件呻粹。這很少被使用,但是能夠在一些復(fù)雜交互的業(yè)務(wù)方法周?chē)峁╊~外的安全層腮郊。
事先和事后授權(quán)在面向?qū)ο笤O(shè)計(jì)中提供了所謂的前置條件和后置條件(preconditions and ostconditions)衅鹿。前置條件和后置條件允許開(kāi)發(fā)者聲明運(yùn)行時(shí)的檢查大渤,從而保證在一個(gè)方法執(zhí)行時(shí)特定的條件需要滿足泵三。在安全的事前授權(quán)和事后授權(quán)中俺抽,業(yè)務(wù)層的開(kāi)發(fā)人員需要對(duì)特定的方法確定明確的安全信息磷斧,并在接口或類(lèi)的API聲明中添加期望的運(yùn)行時(shí)條件弛饭。正如你可能想象的那樣孩哑,這需要大量的規(guī)劃以避免不必要的影響。
保護(hù)業(yè)務(wù)層方法的基本知識(shí)
讓我們以JBCP Pets中業(yè)務(wù)層的幾個(gè)方法為例闡述怎樣為它們應(yīng)用典型的規(guī)則丛晌。
我們對(duì)JBCP Pets的基礎(chǔ)代碼進(jìn)行了重新組織以實(shí)現(xiàn)三層的設(shè)計(jì)澎蛛,作為修改的一部分我們抽象出了前面章節(jié)已經(jīng)介紹到的修改密碼功能到業(yè)務(wù)層谋逻。不同于用web MVC的控制器直接訪問(wèn)JDBC DAO毁兆,我們選擇插入一個(gè)業(yè)務(wù)服務(wù)以提供要求的附加功能。下圖對(duì)此進(jìn)行了描述:
我們能夠看到在例子中
com.packtpub.springsecurity.service.IuserService
接口代表了應(yīng)用架構(gòu)的業(yè)務(wù)層,而這對(duì)我們來(lái)說(shuō)梅桩,是一個(gè)合適位置來(lái)添加方法級(jí)的安全宿百。
添加@PreAuthorize方法注解
我們第一個(gè)的設(shè)計(jì)決策就是要在業(yè)務(wù)層上添加方法安全犀呼,以保證用戶在修改密碼前已經(jīng)作為系統(tǒng)的合法用戶進(jìn)行了登錄坐儿。這通過(guò)為業(yè)務(wù)接口方法定義添加一個(gè)簡(jiǎn)單的注解來(lái)實(shí)現(xiàn)貌矿,如下:
public interface IUserService { @PreAuthorize("hasRole('ROLE_USER')") public void changePassword(String username, String password); }
這就是保證合法、已認(rèn)證的用戶才能訪問(wèn)修改密碼功能所要做的所有事情罪佳。Spring Security將會(huì)使用運(yùn)行時(shí)的面向方面編程的切點(diǎn)(aspect oriented programming (AOP) pointcut)來(lái)對(duì)方法執(zhí)行before advice逛漫,并在安全要求未滿足的情況下拋出AccessDeniedException異常。
讓Spring Security能夠使用方法注解,我們還需要在dogstore-security.xml中做一個(gè)一次性的修改赘艳,通過(guò)這個(gè)文件我們已經(jīng)進(jìn)行了Spring Security其他的配置酌毡。只需要在<http>聲明之前,添加下面的元素即可:
<global-method-security pre-post-annotations="enabled"/>
校驗(yàn)方法安全
不相信如此簡(jiǎn)單蕾管?那我們將ROLE_USER聲明修改為ROLE_ADMIN〖咸ぃ現(xiàn)在用用戶guest(密碼guest)登錄并嘗試修改密碼。你會(huì)在嘗試修改密碼時(shí)掰曾,看到如下的出錯(cuò)界面:
如果查看Tomcat的控制臺(tái)掏熬,你可以看到很長(zhǎng)的堆棧信息岗屏,開(kāi)始是這樣的:
DEBUG - Could not complete request o.s.s.access.AccessDeniedException: Access is denied at o.s.s.access.vote.AffirmativeBased.decide at o.s.s.access.intercept.AbstractSecurityInterceptor.beforeInvocation ... at $Proxy12.changePassword(Unknown Source) at com.packtpub.springsecurity.web.controller.AccountController. submitChangePasswordPage
基于訪問(wèn)拒絕的頁(yè)面以及指向changePassword方法的堆棧信息似袁,我們可以看到用戶被合理的拒絕對(duì)業(yè)務(wù)方法的訪問(wèn),因?yàn)槿鄙賀OLE_ADMIN的GrantedAuthority联予。你可以測(cè)試修改密碼功能對(duì)管理員用戶依舊是可以訪問(wèn)的。
我們只是在接口上添加了簡(jiǎn)單的聲明就能夠保證方法的安全,這是不是太令人興奮了?
讓我們介紹一下實(shí)現(xiàn)方法安全的其它方式吓著,然后進(jìn)入功能的背后以了解其怎樣以及為什么能夠生效纺裁。
幾種實(shí)現(xiàn)方法安全的方式
除了@PreAuthorize注解以外丧鸯,還有幾種其它的方式來(lái)聲明在方法調(diào)用前進(jìn)行授權(quán)檢查的需求。我們會(huì)講解這些實(shí)現(xiàn)方法安全的不同方式,并比較它們?cè)诓煌h(huán)境下的優(yōu)勢(shì)與不足篮愉。
遵守JSR-250標(biāo)準(zhǔn)規(guī)則
JSR-250, Common Annotations for the Java Platform定義了一系列的注解泣刹,其中的一些是安全相關(guān)的,它們意圖在兼容JSR-250的環(huán)境中很方便地使用。Spring框架從Spring 2.x釋放版本開(kāi)始就兼容JSR-250韭邓,包括Spring Security框架诗力。
盡管JSR-250注解不像Spring原生的注解富有表現(xiàn)力纳鼎,但是它們提供的注解能夠兼容不同的Java EE應(yīng)用服務(wù)器實(shí)現(xiàn)如Glassfish映九,或面向服務(wù)的運(yùn)行框架如Apache Tuscany倦逐。取決于你應(yīng)用對(duì)輕便性的需求您单,你可能會(huì)覺(jué)得犧牲代碼的輕便性但減少對(duì)特定環(huán)境的要求是值得的凤优。
要實(shí)現(xiàn)我們?cè)诘谝粋€(gè)例子中的規(guī)則棍辕,我們需要作兩個(gè)修改:
首先在dogstore-security.xml文件中:
<global-method-security jsr250-annotations="enabled"/>
其次,@PreAuthorize注解需要修改成@RolesAllowed注解拍顷。
正如我們可能推斷出的那樣匾乓,@RolesAllowed注解并不支持SpEL表達(dá)式,所以它看起來(lái)很像我們?cè)诘诙?jié)中提到的URL授權(quán)芒帕。我們修改IuserService定義如下:
@RolesAllowed("ROLE_USER") public void changePassword(String username, String password);
正如前面的練習(xí)那樣垛叨,如果不相信它能工作伦糯,嘗試修改ROLE_USER 為ROLE_ADMIN并進(jìn)行測(cè)試。
要注意的是,也可以提供一系列允許的GrantedAuthority名字敛纲,使用Java 5標(biāo)準(zhǔn)的字符串?dāng)?shù)組注解語(yǔ)法:
@RolesAllowed({"ROLE_USER","ROLE_ADMIN"}) public void changePassword(String username, String password);
JSR-250還有兩個(gè)其它的注解:@PermitAll 和@DenyAll喂击。它們的功能正如你所預(yù)想的,允許和禁止對(duì)方法的任何請(qǐng)求载慈。
【類(lèi)層次的注解惭等。注意方法級(jí)別的安全注解也可以使用到類(lèi)級(jí)別上珍手!如果提供了方法級(jí)別的注解办铡,將會(huì)覆蓋類(lèi)級(jí)別的注解。如果業(yè)務(wù)需要在整個(gè)類(lèi)上有安全策略的話琳要,這會(huì)非常有用寡具。要注意的是使用這個(gè)功能要有良好的注釋的編碼規(guī)范,這樣開(kāi)發(fā)人員能夠很清楚的了解類(lèi)和方法的安全特性稚补⊥】
@Secured注解實(shí)現(xiàn)方法安全
Spring本身也提供一個(gè)簡(jiǎn)單的注解,類(lèi)似于JSR-250 的@RolesAllowed注解课幕。
@Secured注解在功能和語(yǔ)法上都與@RolesAllowed一致厦坛。唯一需要注意的不同點(diǎn)是要使用這些注解的話,要在<global-method-security>元素中明確使用另外一個(gè)屬性:
<global-method-security secured-annotations="enabled"/>
因?yàn)锧Secured與JSR標(biāo)準(zhǔn)的@RolesAllowed注解在功能上一致乍惊,所以并沒(méi)有充分的理由在新代碼中使用它杜秸,但是它能夠在Spring的遺留代碼中運(yùn)行。
使用Aspect Oriented Programming (AOP)實(shí)現(xiàn)方法安全
實(shí)現(xiàn)方法安全的最后一項(xiàng)技術(shù)也可能是最強(qiáng)大的方法润绎,它還有一個(gè)好處是不需要修改源代碼撬碟。作為替代,它使用面向方面的編程方式為一個(gè)方法或方法集合聲明切點(diǎn)(pointcut)莉撇,而增強(qiáng)(advice)會(huì)在切點(diǎn)匹配的情況下進(jìn)行基于角色的安全檢查呢蛤。AOP的聲明只在Spring Security的XML配置文件中并不涉及任何的注解。
以下就是聲明保護(hù)所有的service接口只有管理權(quán)限才能訪問(wèn)的例子:
<global-method-security> <protect-pointcut access="ROLE_ADMIN" expression="execution(* com.packtpub.springsecurity.service.I*Service.*(..))"/> </global-method-security>
切點(diǎn)表達(dá)式基于Spring AOP對(duì)AspectJ的支持棍郎。但是其障,Spring AspectJ AOP僅支持AspectJ切點(diǎn)表達(dá)式語(yǔ)言的一個(gè)很小子集——可以參考Spring AOP的文檔以了解其支持的表達(dá)式和其它關(guān)于Spring AOP編程的重要元素。
注意的是涂佃,可以指明一系列的切點(diǎn)聲明励翼,以指向不同的角色和切點(diǎn)目標(biāo)。以下的就是添加切點(diǎn)到DAO中一個(gè)方法的例子:
<global-method-security> <protect-pointcut access="ROLE_USER" expression="execution(* com.packtpub.springsecurity.dao.IProductDao.getCategories(..)) && args()"/> <protect-pointcut access="ROLE_ADMIN" expression="execution(* com.packtpub.springsecurity.service.I*Service.*(..))"/> </global-method-security>
意在新增的切點(diǎn)中巡李,我們添加了一些AspectJ的高級(jí)語(yǔ)法抚笔,來(lái)聲明Boolean邏輯以及其它支持的切點(diǎn),而參數(shù)可以用來(lái)確定參數(shù)的類(lèi)型聲明侨拦。
同Spring Security其它允許一系列安全聲明的地方一樣殊橙,AOP風(fēng)格的方法安全是按照從頂?shù)降椎捻樞蜻M(jìn)行的,所以需要按照最特殊到最不特殊的順序來(lái)寫(xiě)切點(diǎn)。
使用AOP來(lái)進(jìn)行編程即便是經(jīng)驗(yàn)豐富的開(kāi)發(fā)人員可能也會(huì)感到迷惑膨蛮。如果你確定要使用AOP來(lái)進(jìn)行安全聲明叠纹,除了Spring AOP的參考手冊(cè)外,強(qiáng)烈建議你參考一些這個(gè)專(zhuān)題相關(guān)的書(shū)籍敞葛。AOP實(shí)現(xiàn)起來(lái)比較復(fù)雜誉察,尤其是在解決不按照你預(yù)期運(yùn)行的配置錯(cuò)誤時(shí)更是如此。
比較方法授權(quán)的類(lèi)型
以下的快速參考表可能在你選擇授權(quán)方法檢查時(shí)派上用場(chǎng):
大多數(shù)使用Java 5的Spring Security用戶傾向于使用JSR-250注解惹谐,以達(dá)到在IT組織間最大的兼容性和對(duì)業(yè)務(wù)類(lèi)(以及相關(guān)約束)的重用持偏。在需要的地方,這些基本的聲明能夠被Spring Security本身實(shí)現(xiàn)的注解所代替氨肌。
如果你在不支持注解的環(huán)境中(Java 1.4或更早版本)中使用Spring Security鸿秆,很不幸的是,關(guān)于方法安全的執(zhí)行你的選擇可能會(huì)很有限怎囚。即使在這樣的情況下卿叽,對(duì)AOP的使用也提供了相當(dāng)豐富的環(huán)境來(lái)開(kāi)發(fā)基本的安全聲明。
方法的安全保護(hù)是怎樣運(yùn)行的恳守?
方法安全的訪問(wèn)決定機(jī)制——一個(gè)給定的請(qǐng)求是否被允許——在概念上與web請(qǐng)求的訪問(wèn)決定邏輯是相同的考婴。AccessDecisionManager使用一個(gè)AccessDecisionVoters集合,其中每一個(gè)都要對(duì)能否進(jìn)行訪問(wèn)做出允許催烘、拒絕或者棄權(quán)的的投票沥阱。AccessDecisionManager匯集這些投票器的結(jié)果并形成一個(gè)最終能否允許處罰方法的決定。
Web請(qǐng)求的訪問(wèn)決策沒(méi)有這么復(fù)雜颗圣,這是因?yàn)橥ㄟ^(guò)ServletFilters對(duì)安全請(qǐng)求做攔截(以及請(qǐng)求拒絕)都相對(duì)很直接喳钟。因?yàn)榉椒ǖ挠|發(fā)可能發(fā)生在任何的地方,包括沒(méi)有通過(guò)Spring Security直接配置的代碼在岂,Spring Security的設(shè)計(jì)者于是選擇Spring管理的AOP方式來(lái)識(shí)別奔则、評(píng)估以及保護(hù)方法的觸發(fā)。
下圖在總體上展現(xiàn)了方法觸發(fā)授權(quán)決策的主要參與者:
我們 能夠看到Spring Security的.s.s.access.intercept.aopalliance.MethodSecurityInterceptor被標(biāo)準(zhǔn)的Spring AOP運(yùn)行時(shí)觸發(fā)以攔截感興趣的方法調(diào)用蔽午。通過(guò)上面的流程圖易茬,是否允許方法調(diào)用的邏輯就相對(duì)很清晰了。
此時(shí)及老,我們可能會(huì)比較關(guān)心方法安全功能的性能抽莱。顯然,MethodSecurityInterceptor不能在應(yīng)用中每個(gè)方法調(diào)用的時(shí)候觸發(fā)——那方法或類(lèi)上的注解是如何做到AOP攔截的呢骄恶?
首先食铐,AOP織入默認(rèn)不會(huì)對(duì)所有Spring管理的bean觸發(fā)。相反僧鲁,如果<global-method-security>在Spring Security配置中定義虐呻,一個(gè)標(biāo)準(zhǔn)的Spring AOP o.s.beans.factory.config.BeanPostProcessor將會(huì)被注冊(cè)象泵,它將會(huì)探查AOP配置是否有AOP增強(qiáng)器(advisors)需要織入(以及攔截)。這個(gè)工作流是Spring標(biāo)準(zhǔn)的AOP處理(名為AOP自動(dòng)織入)斟叼,并不是Spring Security所特有的偶惠。所有的BeanPostProcessors在spring ApplicationContext初始化時(shí)執(zhí)行,在所有的Spring Bean配置生效后朗涩。
Spring的AOP自動(dòng)織入功能查詢所有注冊(cè)的PointcutAdvisors忽孽,查看是否有AOP切點(diǎn)匹配方法的調(diào)用并使用AOP增強(qiáng)(advice)。Spring Security實(shí)現(xiàn)了o.s.s.access.intercept.aopalliance.MethodSecurityMetadataSourceAdvisor類(lèi)谢床,它會(huì)檢查所有配置的方法安全病建立適當(dāng)?shù)腁OP攔截兄一。注意的是,只有聲明了方法安全的接口和類(lèi)才會(huì)被AOP代理萤悴。
【強(qiáng)烈建議在接口上聲明AOP規(guī)則(以及其它的安全注解)瘾腰,而不是在實(shí)現(xiàn)類(lèi)上。使用類(lèi)(通過(guò)Spring的CGLIB代理)進(jìn)行聲明可能會(huì)導(dǎo)致應(yīng)用出現(xiàn)不可預(yù)知的行為改變覆履,通常在正確性方面比不上在接口定義安全聲明(通過(guò)AOP)》驯。】
MethodSecurityMetadataSourceAdvisor將AOP影響方法行為的決定委托給.s.s.access.method.MethodSecurityMetadataSource的實(shí)例硝全。不同的方法安全注解都擁有自己的MethodSecurityMetadataSource,它將用來(lái)檢查每個(gè)方法和類(lèi)并添加在運(yùn)行時(shí)執(zhí)行的增強(qiáng)(advice)楞抡。
以下的圖展現(xiàn)了這個(gè)過(guò)程是如何發(fā)生的:
取決于你的應(yīng)用中配置的Sprin Bean的數(shù)量伟众,以及擁有的安全方法注解的數(shù)量,添加方法安全代理將會(huì)增加初始化ApplicationContext的時(shí)間召廷。但是凳厢,一旦上下文初始化完成,對(duì)單個(gè)的代理bean來(lái)說(shuō)性能的影響可以忽略不計(jì)了竞慢。