javascript
【Spring】12、Spring Security 四种使用方式
spring security使用分類(lèi):
如何使用spring security,相信百度過(guò)的都知道,總共有四種用法,從簡(jiǎn)到深為:1、不用數(shù)據(jù)庫(kù),全部數(shù)據(jù)寫(xiě)在配置文件,這個(gè)也是官方文檔里面的demo;2、使用數(shù)據(jù)庫(kù),根據(jù)spring security默認(rèn)實(shí)現(xiàn)代碼設(shè)計(jì)數(shù)據(jù)庫(kù),也就是說(shuō)數(shù)據(jù)庫(kù)已經(jīng)固定了,這種方法不靈活,而且那個(gè)數(shù)據(jù)庫(kù)設(shè)計(jì)得很簡(jiǎn)陋,實(shí)用性差;3、spring security和Acegi不同,它不能修改默認(rèn)filter了,但支持插入filter,所以根據(jù)這個(gè),我們可以插入自己的filter來(lái)靈活使用;4、暴力手段,修改源碼,前面說(shuō)的修改默認(rèn)filter只是修改配置文件以替換filter而已,這種是直接改了里面的源碼,但是這種不符合OO設(shè)計(jì)原則,而且不實(shí)際,不可用。
本文面向讀者:
因?yàn)楸疚臏?zhǔn)備介紹第三種方法,所以面向的讀者是已經(jīng)具備了spring security基礎(chǔ)知識(shí)的。不過(guò)不要緊,讀者可以先看一下這個(gè)教程,看完應(yīng)該可以使用第二種方法開(kāi)發(fā)了。
spring security的簡(jiǎn)單原理:
使用眾多的攔截器對(duì)url攔截,以此來(lái)管理權(quán)限。但是這么多攔截器,筆者不可能對(duì)其一一來(lái)講,主要講里面核心流程的兩個(gè)。 首先,權(quán)限管理離不開(kāi)登陸驗(yàn)證的,所以登陸驗(yàn)證攔截器AuthenticationProcessingFilter要講; 還有就是對(duì)訪(fǎng)問(wèn)的資源管理吧,所以資源管理攔截器AbstractSecurityInterceptor要講; 但攔截器里面的實(shí)現(xiàn)需要一些組件來(lái)實(shí)現(xiàn),所以就有了AuthenticationManager、accessDecisionManager等組件來(lái)支撐。 現(xiàn)在先大概過(guò)一遍整個(gè)流程,用戶(hù)登陸,會(huì)被AuthenticationProcessingFilter攔截,調(diào)用AuthenticationManager的實(shí)現(xiàn),而且AuthenticationManager會(huì)調(diào)用ProviderManager來(lái)獲取用戶(hù)驗(yàn)證信息(不同的Provider調(diào)用的服務(wù)不同,因?yàn)檫@些信息可以是在數(shù)據(jù)庫(kù)上,可以是在LDAP服務(wù)器上,可以是xml配置文件上等),如果驗(yàn)證通過(guò)后會(huì)將用戶(hù)的權(quán)限信息封裝一個(gè)User放到spring的全局緩存SecurityContextHolder中,以備后面訪(fǎng)問(wèn)資源時(shí)使用。 訪(fǎng)問(wèn)資源(即授權(quán)管理),訪(fǎng)問(wèn)url時(shí),會(huì)通過(guò)AbstractSecurityInterceptor攔截器攔截,其中會(huì)調(diào)用FilterInvocationSecurityMetadataSource的方法來(lái)獲取被攔截url所需的全部權(quán)限,在調(diào)用授權(quán)管理器AccessDecisionManager,這個(gè)授權(quán)管理器會(huì)通過(guò)spring的全局緩存SecurityContextHolder獲取用戶(hù)的權(quán)限信息,還會(huì)獲取被攔截的url和被攔截url所需的全部權(quán)限,然后根據(jù)所配的策略(有:一票決定,一票否定,少數(shù)服從多數(shù)等),如果權(quán)限足夠,則返回,權(quán)限不夠則報(bào)錯(cuò)并調(diào)用權(quán)限不足頁(yè)面。 雖然講得好像好復(fù)雜,讀者們可能有點(diǎn)暈,不過(guò)不打緊,真正通過(guò)代碼的講解在后面,讀者可以看完后面的代碼實(shí)現(xiàn),再返回看這個(gè)簡(jiǎn)單的原理,可能會(huì)有不錯(cuò)的收獲。?
spring security使用實(shí)現(xiàn)(基于spring security3.1.4):
javaEE的入口:web.xml:
?| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | <!--?xml version="1.0" encoding="UTF-8"?--> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemalocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> ?????<!--加載Spring XML配置文件 --> ????<context-param> ????????<param-name>contextConfigLocation</param-name> ????????<param-value> classpath:securityConfig.xml?????????? </param-value> ????</context-param> ??????<!-- Spring Secutiry3.1的過(guò)濾器鏈配置 --> ????<filter> ????????<filter-name>springSecurityFilterChain</filter-name> ????????<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> ????</filter> ????<filter-mapping> ????????<filter-name>springSecurityFilterChain</filter-name> ????????<url-pattern>/*</url-pattern> ????</filter-mapping> ???????<!-- Spring 容器啟動(dòng)監(jiān)聽(tīng)器 --> ????<listener> ????????<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> ????</listener>?? ??????<!--系統(tǒng)歡迎頁(yè)面 --> ????<welcome-file-list> ????????<welcome-file>index.jsp</welcome-file> ????</welcome-file-list> </web-app> |
上面那個(gè)配置不用多說(shuō)了吧 直接上spring security的配置文件securityConfig.xml:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | <!--?xml version="1.0" encoding="UTF-8"?--> <b:beans xmlns="http://www.springframework.org/schema/security" xmlns:b="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemalocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd ????????????????????????http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd"> ??<!--登錄頁(yè)面不過(guò)濾 --> ????<http pattern="/login.jsp" security="none"> ????<http access-denied-page="/accessDenied.jsp"> ????????<form-login login-page="/login.jsp"> ????????<!--訪(fǎng)問(wèn)/http://blog.csdn.net/u012367513/article/details/admin.jsp資源的用戶(hù)必須具有ROLE_ADMIN的權(quán)限 --> ????????<!-- <intercept-url pattern="/http://blog.csdn.net/u012367513/article/details/admin.jsp" access="ROLE_ADMIN" /> --> ????????<!--訪(fǎng)問(wèn)/**資源的用戶(hù)必須具有ROLE_USER的權(quán)限 --> ????????<!-- <intercept-url pattern="/**" access="ROLE_USER" /> --> ????????<session-management> ????????????<concurrency-control max-sessions="1" error-if-maximum-exceeded="false"> ????????</concurrency-control></session-management> ????????<!--增加一個(gè)filter,這點(diǎn)與 Acegi是不一樣的,不能修改默認(rèn)的filter了, 這個(gè)filter位于FILTER_SECURITY_INTERCEPTOR之前 --> ????????<custom-filter ref="myFilter" before="FILTER_SECURITY_INTERCEPTOR"> ????</custom-filter></form-login></http> ????<!--一個(gè)自定義的filter,必須包含 authenticationManager,accessDecisionManager,securityMetadataSource三個(gè)屬性, ????????我們的所有控制將在這三個(gè)類(lèi)中實(shí)現(xiàn),解釋詳見(jiàn)具體配置 --> ????<b:bean id="myFilter" class="com.erdangjiade.spring.security.MyFilterSecurityInterceptor"> ????????<b:property name="authenticationManager" ref="authenticationManager"> ????????<b:property name="accessDecisionManager" ref="myAccessDecisionManagerBean"> ????????<b:property name="securityMetadataSource" ref="securityMetadataSource"> ????</b:property></b:property></b:property></b:bean> ????<!--驗(yàn)證配置,認(rèn)證管理器,實(shí)現(xiàn)用戶(hù)認(rèn)證的入口,主要實(shí)現(xiàn)UserDetailsService接口即可 --> ????? ????????? ????????????<!--如果用戶(hù)的密碼采用加密的話(huà) <password-encoder hash="md5" /> --> ????????</authentication-provider> ????</authentication-manager> ????<!--在這個(gè)類(lèi)中,你就可以從數(shù)據(jù)庫(kù)中讀入用戶(hù)的密碼,角色信息,是否鎖定,賬號(hào)是否過(guò)期等 --> ????<b:bean id="myUserDetailService" class="com.erdangjiade.spring.security.MyUserDetailService"> ????<!--訪(fǎng)問(wèn)決策器,決定某個(gè)用戶(hù)具有的角色,是否有足夠的權(quán)限去訪(fǎng)問(wèn)某個(gè)資源 --> ????<b:bean id="myAccessDecisionManagerBean" class="com.erdangjiade.spring.security.MyAccessDecisionManager"> ????</b:bean> ????<!--資源源數(shù)據(jù)定義,將所有的資源和權(quán)限對(duì)應(yīng)關(guān)系建立起來(lái),即定義某一資源可以被哪些角色訪(fǎng)問(wèn) --> ????<b:bean id="securityMetadataSource" class="com.erdangjiade.spring.security.MyInvocationSecurityMetadataSource"> ??????????? ?</b:bean></b:bean></http></b:beans> |
其實(shí)所有配置都在里面,首先這個(gè)版本的spring security不支持了filter=none的配置了,改成了獨(dú)立的,里面你可以配登陸頁(yè)面、權(quán)限不足的返回頁(yè)面、注銷(xiāo)頁(yè)面等,上面那些配置,我注銷(xiāo)了一些資源和權(quán)限的對(duì)應(yīng)關(guān)系,筆者這里不需要在這配死它,可以自己寫(xiě)攔截器來(lái)獲得資源與權(quán)限的對(duì)應(yīng)關(guān)系。 session-management是用來(lái)防止多個(gè)用戶(hù)同時(shí)登陸一個(gè)賬號(hào)的。
最重要的是筆者自己寫(xiě)的攔截器myFilter(終于講到重點(diǎn)了),首先這個(gè)攔截器會(huì)加載在FILTER_SECURITY_INTERCEPTOR之前(配置文件上有說(shuō)),最主要的是這個(gè)攔截器里面配了三個(gè)處理類(lèi),第一個(gè)是authenticationManager,這個(gè)是處理驗(yàn)證的,這里需要特別說(shuō)明的是:這個(gè)類(lèi)不單只這個(gè)攔截器用到,還有驗(yàn)證攔截器AuthenticationProcessingFilter也用到 了,而且實(shí)際上的登陸驗(yàn)證也是AuthenticationProcessingFilter攔截器調(diào)用authenticationManager來(lái)處理的,我們這個(gè)攔截器只是為了拿到驗(yàn)證用戶(hù)信息而已(這里不太清楚,因?yàn)閍uthenticationManager筆者設(shè)了斷點(diǎn),用戶(hù)登陸后再也沒(méi)調(diào)用這個(gè)類(lèi)了,而且調(diào)用這個(gè)類(lèi)時(shí)不是筆者自己寫(xiě)的那個(gè)攔截器調(diào)用的,看了spring技術(shù)內(nèi)幕這本書(shū)才知道是AuthenticationProcessingFilter攔截器調(diào)用的)。 securityMetadataSource這個(gè)用來(lái)加載資源與權(quán)限的全部對(duì)應(yīng)關(guān)系的,并提供一個(gè)通過(guò)資源獲取所有權(quán)限的方法。
accessDecisionManager這個(gè)也稱(chēng)為授權(quán)器,通過(guò)登錄用戶(hù)的權(quán)限信息、資源、獲取資源所需的權(quán)限來(lái)根據(jù)不同的授權(quán)策略來(lái)判斷用戶(hù)是否有權(quán)限訪(fǎng)問(wèn)資源。
authenticationManager類(lèi)可以有許多provider(提供者)提供用戶(hù)驗(yàn)證信息,這里筆者自己寫(xiě)了一個(gè)類(lèi)myUserDetailService來(lái)獲取用戶(hù)信息。
MyUserDetailService:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | package com.erdangjiade.spring.security; import java.util.ArrayList; import java.util.Collection; import org.springframework.dao.DataAccessException; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.GrantedAuthorityImpl; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; public class MyUserDetailService implements UserDetailsService { ????? ????//登陸驗(yàn)證時(shí),通過(guò)username獲取用戶(hù)的所有權(quán)限信息, ????//并返回User放到spring的全局緩存SecurityContextHolder中,以供授權(quán)器使用 ????public UserDetails loadUserByUsername(String username) ????????????throws UsernameNotFoundException, DataAccessException {?? ????????Collection<grantedauthority> auths=new ArrayList<grantedauthority>(); ????????? ????????GrantedAuthorityImpl auth2=new GrantedAuthorityImpl("ROLE_ADMIN"); ????????GrantedAuthorityImpl auth1=new GrantedAuthorityImpl("ROLE_USER"); ????????? ????????if(username.equals("lcy")){ ????????????auths=new ArrayList<grantedauthority>(); ????????????auths.add(auth1); ????????????auths.add(auth2);????? ????????}???? ????????? ????????User user = new User(username, "lcy", true, true, true, true, auths); ????????return user;? ????????} ????} </grantedauthority></grantedauthority></grantedauthority> |
其中UserDetailsService接口是spring提供的,必須實(shí)現(xiàn)的。別看這個(gè)類(lèi)只有一個(gè)方法,而且這么簡(jiǎn)單,其中內(nèi)涵玄機(jī)。 讀者看到這里可能就大感疑惑了,不是說(shuō)好的用數(shù)據(jù)庫(kù)嗎?對(duì),但別急,等筆者慢慢給你們解析。 首先,筆者為什么不用數(shù)據(jù)庫(kù),還不是為了讀者們測(cè)試方便,并簡(jiǎn)化spring security的流程,讓讀者抓住主線(xiàn),而不是還要煩其他事(導(dǎo)入數(shù)據(jù)庫(kù),配置數(shù)據(jù)庫(kù),寫(xiě)dao等)。 這里筆者只是用幾個(gè)數(shù)據(jù)模擬了從數(shù)據(jù)庫(kù)中拿到的數(shù)據(jù),也就是說(shuō)ROLE_ADMIN、ROLE_USER、lcy(第一個(gè)是登陸賬號(hào))、lcy(第二個(gè)是密碼)是從數(shù)據(jù)庫(kù)拿出來(lái)的,這個(gè)不難實(shí)現(xiàn)吧,如果需要數(shù)據(jù)庫(kù)時(shí),讀者可以用自己寫(xiě)的dao通過(guò)參數(shù)username來(lái)查詢(xún)出這個(gè)用戶(hù)的權(quán)限信息(或是角色信息,就是那個(gè)ROLE_*,對(duì)必須是ROLE_開(kāi)頭的,不然spring security不認(rèn)賬的,其實(shí)是spring security里面做了一個(gè)判斷,必須要ROLE_開(kāi)頭,讀者可以百度改一下),再返回spring自帶的數(shù)據(jù)模型User即可。 這個(gè)寫(xiě)應(yīng)該比較清晰、靈活吧,總之?dāng)?shù)據(jù)讀者們通過(guò)什么方法獲取都行,只要返回一個(gè)User對(duì)象就行了。(這也是筆者為什么要重寫(xiě)這個(gè)類(lèi)的原因)?
通過(guò)MyUserDetailService拿到用戶(hù)信息后,authenticationManager對(duì)比用戶(hù)的密碼(即驗(yàn)證用戶(hù)),然后這個(gè)AuthenticationProcessingFilter攔截器就過(guò)咯。?
下面要說(shuō)的是另外一個(gè)攔截器,就是筆者自己寫(xiě)的攔截器MyFilterSecurityInterceptor:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | package com.erdangjiade.spring.security; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import org.springframework.security.access.SecurityMetadataSource; import org.springframework.security.access.intercept.AbstractSecurityInterceptor; import org.springframework.security.access.intercept.InterceptorStatusToken; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor? implements Filter {? ????? ????//配置文件注入 ????private FilterInvocationSecurityMetadataSource securityMetadataSource; ????? ????//登陸后,每次訪(fǎng)問(wèn)資源都通過(guò)這個(gè)攔截器攔截 ????public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { ????????FilterInvocation fi = new FilterInvocation(request, response, chain); ????????invoke(fi);? ????????} ????? ????public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {? ????????return this.securityMetadataSource;? ????????}?? ????? ????public Class<!--? extends Object--> getSecureObjectClass() { ????????return FilterInvocation.class;??? ????????}? ????? ????public void invoke(FilterInvocation fi) throws IOException, ServletException { ????????//fi里面有一個(gè)被攔截的url ????????//里面調(diào)用MyInvocationSecurityMetadataSource的getAttributes(Object object)這個(gè)方法獲取fi對(duì)應(yīng)的所有權(quán)限 ????????//再調(diào)用MyAccessDecisionManager的decide方法來(lái)校驗(yàn)用戶(hù)的權(quán)限是否足夠 ????????InterceptorStatusToken token = super.beforeInvocation(fi); ????????try { ????????????//執(zhí)行下一個(gè)攔截器 ????????????fi.getChain().doFilter(fi.getRequest(), fi.getResponse());?? ????????????} finally { ????????????????super.afterInvocation(token, null);? ????????????}?? ????????}? ????public SecurityMetadataSource obtainSecurityMetadataSource() { ????????return this.securityMetadataSource;?? ????????} ????public void setSecurityMetadataSource( ????????????FilterInvocationSecurityMetadataSource newSource) ????{ ????????this.securityMetadataSource = newSource; ????} ????public void destroy() {? ????????? ????}?? ????public void init(FilterConfig arg0) throws ServletException {? ????????? ????}? } |
繼承AbstractSecurityInterceptor、實(shí)現(xiàn)Filter是必須的。 首先,登陸后,每次訪(fǎng)問(wèn)資源都會(huì)被這個(gè)攔截器攔截,會(huì)執(zhí)行doFilter這個(gè)方法,這個(gè)方法調(diào)用了invoke方法,其中fi斷點(diǎn)顯示是一個(gè)url(可能重寫(xiě)了toString方法吧,但是里面還有一些方法的),最重要的是beforeInvocation這個(gè)方法,它首先會(huì)調(diào)用MyInvocationSecurityMetadataSource類(lèi)的getAttributes方法獲取被攔截url所需的權(quán)限,在調(diào)用MyAccessDecisionManager類(lèi)decide方法判斷用戶(hù)是否夠權(quán)限。弄完這一切就會(huì)執(zhí)行下一個(gè)攔截器。?
再看一下這個(gè)MyInvocationSecurityMetadataSource的實(shí)現(xiàn):
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | package com.erdangjiade.spring.security; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; import com.erdangjiade.spring.security.tool.AntUrlPathMatcher; import com.erdangjiade.spring.security.tool.UrlMatcher; public class MyInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { ????private UrlMatcher urlMatcher = new AntUrlPathMatcher(); ????private static Map<string, collection<configattribute="">> resourceMap = null; ????? ????//tomcat啟動(dòng)時(shí)實(shí)例化一次 ????public MyInvocationSecurityMetadataSource() { ????????loadResourceDefine();? ????????}?? ????//tomcat開(kāi)啟時(shí)加載一次,加載所有url和權(quán)限(或角色)的對(duì)應(yīng)關(guān)系 ????private void loadResourceDefine() { ????????resourceMap = new HashMap<string, collection<configattribute="">>(); ????????Collection<configattribute> atts = new ArrayList<configattribute>(); ????????ConfigAttribute ca = new SecurityConfig("ROLE_USER"); ????????atts.add(ca); ????????resourceMap.put("/index.jsp", atts);? ????????Collection<configattribute> attsno =new ArrayList<configattribute>(); ????????ConfigAttribute cano = new SecurityConfig("ROLE_NO"); ????????attsno.add(cano); ????????resourceMap.put("/http://blog.csdn.net/u012367513/article/details/other.jsp", attsno);?? ????????}? ????? ????//參數(shù)是要訪(fǎng)問(wèn)的url,返回這個(gè)url對(duì)于的所有權(quán)限(或角色) ????public Collection<configattribute> getAttributes(Object object) throws IllegalArgumentException { ????????// 將參數(shù)轉(zhuǎn)為url??? ????????String url = ((FilterInvocation)object).getRequestUrl();?? ????????Iterator<string>ite = resourceMap.keySet().iterator(); ????????while (ite.hasNext()) {???????? ????????????String resURL = ite.next();? ????????????if (urlMatcher.pathMatchesUrl(resURL, url)) { ????????????????return resourceMap.get(resURL);???????? ????????????????}?????? ????????????} ????????return null;??? ????????}? ????public boolean supports(Class<!--?-->clazz) { ????????????return true;? ????????????} ????public Collection<configattribute> getAllConfigAttributes() { ????????return null;? ????????} ????} </configattribute></string></configattribute></configattribute></configattribute></configattribute></configattribute></string,></string,> |
實(shí)現(xiàn)FilterInvocationSecurityMetadataSource接口也是必須的。 首先,這里也是模擬了從數(shù)據(jù)庫(kù)中獲取信息。 其中l(wèi)oadResourceDefine方法不是必須的,這個(gè)只是加載所有的資源與權(quán)限的對(duì)應(yīng)關(guān)系并緩存起來(lái),避免每次獲取權(quán)限都訪(fǎng)問(wèn)數(shù)據(jù)庫(kù)(提高性能),然后getAttributes根據(jù)參數(shù)(被攔截url)返回權(quán)限集合。 這種緩存的實(shí)現(xiàn)其實(shí)有一個(gè)缺點(diǎn),因?yàn)閘oadResourceDefine方法是放在構(gòu)造器上調(diào)用的,而這個(gè)類(lèi)的實(shí)例化只在web服務(wù)器啟動(dòng)時(shí)調(diào)用一次,那就是說(shuō)loadResourceDefine方法只會(huì)調(diào)用一次,如果資源和權(quán)限的對(duì)應(yīng)關(guān)系在啟動(dòng)后發(fā)生了改變,那么緩存起來(lái)的就是臟數(shù)據(jù),而筆者這里使用的就是緩存數(shù)據(jù),那就會(huì)授權(quán)錯(cuò)誤了。但如果資源和權(quán)限對(duì)應(yīng)關(guān)系是不會(huì)改變的,這種方法性能會(huì)好很多。 現(xiàn)在說(shuō)回有數(shù)據(jù)庫(kù)的靈活實(shí)現(xiàn),讀者看到這,可能會(huì)說(shuō),這還不簡(jiǎn)單,和上面MyUserDetailService類(lèi)一樣使用dao靈活獲取數(shù)據(jù)就行啦。 如果讀者這樣想,那只想到了一半,想一下spring的機(jī)制(依賴(lài)注入),dao需要依賴(lài)注入吧,但這是在啟動(dòng)時(shí)候,那個(gè)dao可能都還沒(méi)加載,所以這里需要讀者自己寫(xiě)sessionFactory,自己寫(xiě)hql或sql,對(duì),就在loadResourceDefine方法里面寫(xiě)(這個(gè)應(yīng)該會(huì)寫(xiě)吧,基礎(chǔ)來(lái)的)。那如果說(shuō)想用第二種方法呢(就是允許資源和權(quán)限的對(duì)應(yīng)關(guān)系改變的那個(gè)),那更加簡(jiǎn)單,根本不需要loadResourceDefine方法了,直接在getAttributes方法里面調(diào)用dao(這個(gè)是加載完,后來(lái)才會(huì)調(diào)用的,所以可以使用dao),通過(guò)被攔截url獲取數(shù)據(jù)庫(kù)中的所有權(quán)限,封裝成Collection返回就行了。(靈活、簡(jiǎn)單) 注意:接口UrlMatcher和實(shí)現(xiàn)類(lèi)AntUrlPathMatcher是筆者自己寫(xiě)的,這本來(lái)是spring以前版本有的,現(xiàn)在沒(méi)有了,但是覺(jué)得好用就用會(huì)來(lái)了,直接上代碼(讀者也可以自己寫(xiě)正則表達(dá)式驗(yàn)證被攔截url和緩存或數(shù)據(jù)庫(kù)的url是否匹配):
| 1 2 3 4 5 6 7 8 | package com.erdangjiade.spring.security.tool; public interface UrlMatcher{ ????Object compile(String paramString); ????boolean pathMatchesUrl(Object paramObject, String paramString); ????String getUniversalMatchPattern(); ????boolean requiresLowerCaseUrl(); } |
?
?| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | package com.erdangjiade.spring.security.tool; import org.springframework.util.AntPathMatcher; import org.springframework.util.PathMatcher; ??public class AntUrlPathMatcher implements UrlMatcher {? ??????private boolean requiresLowerCaseUrl; ??????private PathMatcher pathMatcher; ??????public AntUrlPathMatcher()?? { ??????????this(true); ??????? ??}? ??????public AntUrlPathMatcher(boolean requiresLowerCaseUrl) ??????{? ??????????this.requiresLowerCaseUrl = true; ??????this.pathMatcher = new AntPathMatcher(); ??????this.requiresLowerCaseUrl = requiresLowerCaseUrl; ??????} ??????? ??????public Object compile(String path) { ??????????if (this.requiresLowerCaseUrl) { ??????????????return path.toLowerCase();? ??????????????}?? ??????????return path;? ??????}? ??????? ??????public void setRequiresLowerCaseUrl(boolean requiresLowerCaseUrl){ ??????????? ??????????this.requiresLowerCaseUrl = requiresLowerCaseUrl; ??????} ??????? ??????public boolean pathMatchesUrl(Object path, String url) { ??????????if (("/**".equals(path)) || ("**".equals(path))) { ??????????????return true;???? ??????????????}? ??????????? ??????????return this.pathMatcher.match((String)path, url); ??????} ??????? ??????public String getUniversalMatchPattern() { ??????????return"/**";? ??????} ??????? ??????public boolean requiresLowerCaseUrl() { ??????????return this.requiresLowerCaseUrl;? ??????}? ??????? ??????public String toString() {? ??????????return super.getClass().getName() + "[requiresLowerCase='" ??????+ this.requiresLowerCaseUrl + "']";? ??????} ??} |
然后MyAccessDecisionManager類(lèi)的實(shí)現(xiàn):
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | package com.erdangjiade.spring.security; import java.util.Collection; import java.util.Iterator; import org.springframework.security.access.AccessDecisionManager; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.authentication.InsufficientAuthenticationException; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; public class MyAccessDecisionManager implements AccessDecisionManager { ????? ????//檢查用戶(hù)是否夠權(quán)限訪(fǎng)問(wèn)資源 ????//參數(shù)authentication是從spring的全局緩存SecurityContextHolder中拿到的,里面是用戶(hù)的權(quán)限信息 ????//參數(shù)object是url ????//參數(shù)configAttributes所需的權(quán)限 ????public void decide(Authentication authentication, Object object,??? ????????????Collection<configattribute> configAttributes) ????????????????????throws AccessDeniedException, InsufficientAuthenticationException { ????????if(configAttributes == null){ ????????????return;?????? ????????}? ????????? ????????Iterator<configattribute> ite=configAttributes.iterator(); ????????while(ite.hasNext()){ ????????????ConfigAttribute ca=ite.next();? ????????????String needRole=((SecurityConfig)ca).getAttribute(); ????????????for(GrantedAuthority ga : authentication.getAuthorities()){ ????????????????if(needRole.equals(ga.getAuthority())){? ????????????????????? ????????????????????return;????????????? ????????}??????????? ????}????? } ????????//注意:執(zhí)行這里,后臺(tái)是會(huì)拋異常的,但是界面會(huì)跳轉(zhuǎn)到所配的access-denied-page頁(yè)面 ????????throw new AccessDeniedException("no right");?? }?? ????public boolean supports(ConfigAttribute attribute) { ????????return true; ????}? ????public boolean supports(Class<!--?-->clazz) { ????????return true; ????????} ????}</configattribute></configattribute> |
接口A(yíng)ccessDecisionManager也是必須實(shí)現(xiàn)的。 decide方法里面寫(xiě)的就是授權(quán)策略了,筆者的實(shí)現(xiàn)是,沒(méi)有明說(shuō)需要權(quán)限的(即沒(méi)有對(duì)應(yīng)的權(quán)限的資源),可以訪(fǎng)問(wèn),用戶(hù)具有其中一個(gè)或多個(gè)以上的權(quán)限的可以訪(fǎng)問(wèn)。這個(gè)就看需求了,需要什么策略,讀者可以自己寫(xiě)其中的策略邏輯。通過(guò)就返回,不通過(guò)拋異常就行了,spring security會(huì)自動(dòng)跳到權(quán)限不足頁(yè)面(配置文件上配的)。?
就這樣,整個(gè)流程過(guò)了一遍。?
剩下的頁(yè)面代碼
本來(lái)想給這個(gè)demo的源碼出來(lái)的,但是筆者覺(jué)得,通過(guò)這個(gè)教程一步一步讀下來(lái),并自己敲一遍代碼,會(huì)比直接運(yùn)行一遍demo印象更深刻,并且更容易理解里面的原理。 而且我的源碼其實(shí)都公布出來(lái)了: login.jsp:
?| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <%@page language="java" import="java.util.*" pageEncoding="UTF-8"%> <title>登錄</title> ????<form action="j_spring_security_check" method="POST"> ????<table> ????????<tbody><tr> ????????????<td>用戶(hù):</td> ????????????<td><input type="'text'" name="'j_username'"></td> ????????</tr> ????????<tr> ????????????<td>密碼:</td> ????????????<td><input type="'password'" name="'j_password'"></td> ????????</tr> ????????<tr> ????????????<td><input name="reset" type="reset"></td> ????????????<td><input name="submit" type="submit"></td> ????????</tr> ????</tbody></table> ????</form> |
index.jsp:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | <%@page language="java" import="java.util.*" pageEncoding="UTF-8"%>? <%@taglib prefix="sec" uri="http://www.springframework.org/security/tags"%>? ?? ?? ?? <title>My?JSP?'index.jsp'?starting?page</title>? ?? ????? <h3>這是首頁(yè)</h3>歡迎 ????<sec:authentication property="name"> ! ????? ????<br>? ????進(jìn)入admin頁(yè)面? ????進(jìn)入其它頁(yè)面? ?? ?? </sec:authentication> |
http://blog.csdn.net/u012367513/article/details/admin.jsp:
?| 1 2 3 4 5 6 7 8 9 | <%@page language="java" import="java.util.*" pageEncoding="utf-8"%> <title>My JSP 'http://blog.csdn.net/u012367513/article/details/admin.jsp' starting page</title> ????歡迎來(lái)到管理員頁(yè)面. ????<br> |
accessDenied.jsp:
| 1 2 3 4 5 6 7 8 9 | <%@page language="java" import="java.util.*" pageEncoding="utf-8"%> <title>My JSP 'http://blog.csdn.net/u012367513/article/details/admin.jsp' starting page</title> ????歡迎來(lái)到管理員頁(yè)面. ????<br> |
http://blog.csdn.net/u012367513/article/details/other.jsp:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <% String path = request.getContextPath(); String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; %> ??? ????<base href="<%=basePath%>"> ????? ????<title>My JSP 'http://blog.csdn.net/u012367513/article/details/other.jsp' starting page</title> ????? ????<meta http-equiv="pragma" content="no-cache"> ????<meta http-equiv="cache-control" content="no-cache"> ????<meta http-equiv="expires" content="0">??? ????<meta http-equiv="keywords" content="keyword1,keyword2,keyword3"> ????<meta http-equiv="description" content="This is my page"> ????<!-- ????<link rel="stylesheet" type="text/css" href="styles.css"> ????--> ??? ??? ??? ????<h3>這里是Other頁(yè)面</h3> ?? |
項(xiàng)目圖:
<img src="http://www.2cto.com/uploadfile/Collfiles/20140829/20140829091240286.png" alt="n峨n竩�漽j喎�" http:="" www.2cto.com="" ym"="" target="_blank" class="keylink" style="border-width: 0px; padding: 0px; margin: 0px; list-style: none; width: 322px; height: 464px;">源碼和jar包都在這個(gè)教程里面,為什么不直接給?筆者的目的是讓讀者跟著教程敲一遍代碼,使印象深刻(相信做這行的都知道,同樣一段代碼,看過(guò)和敲過(guò)的區(qū)別是多么的大),所以不惜如此來(lái)強(qiáng)迫大家了。?
轉(zhuǎn)自:http://blog.csdn.net/u013516966/article/details/46688765
總結(jié)
以上是生活随笔為你收集整理的【Spring】12、Spring Security 四种使用方式的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 服务器双网卡冗余备份技术的实现
- 下一篇: 将weex项目打包的关键点