最近由於一直在用的一個系統,在登陸是總是出現某個問題,而這個系統是用CAS實現的單點登陸。因而,就又回去從新瞭解一邊CAS的認證流程,以及在Spring Security上的實現。此次回顧,讓我對整個流程有了更深刻的理解。在這裏特地作一個總結和記錄。css
本人的另外一篇講述Spring Security3.1配置的文章,描述的也很是詳細。讀者可結合兩篇文章一塊兒學習。文章地址:http://flyingsnail.blog.51cto.com/5341669/1317752html
第1、二部分的資料來自於網絡,在此做爲參考。
java
1、 CAS 簡介 web
1.1 CAS 是什麼 spring
CAS (Central Authentication Service) 是 Yale 大學發起的一個 Java 開源項目,旨在爲 Web 應用系統提供一種可靠的 單點登陸 解決方案( Web SSO ), CAS 在 2004 年 12 月正式成爲 JA-SIG 的一個項目。 CAS 具備如下特色: 數據庫
一、 開源的企業級單點登陸解決方案; api
二、 CAS Server 爲須要獨立部署的 Web 應用; 瀏覽器
三、 CAS Client 支持很是多的客戶端 ( 指單點登陸系統中的各個 Web 應用 ) ,包括 Java, .Net, PHP, Perl, 等。 緩存
1.2 CAS 原理 安全
從結構上看, CAS 包含兩個部分: CAS Server 和 CAS Client 。
CAS Server 須要獨立部署,主要負責對用戶的認證工做, 它會處理用戶名 / 密碼等憑證 (Credentials) ;
CAS Client 部署在客戶端, 負責處理 對本地 Web 應用(客戶端)受保護資源的訪問請求,而且當須要對請求方進行身份認證時,重定向到 CAS Server 進行認證, 下圖是 CAS 最基礎協議:
1 、 CAS Client 與受保護的客戶端應用部署在一塊兒,以 Filter 方式保護受保護的資源。對於訪問受保護資源的每一個 Web 請求, CAS Client 會分析該請求的 Http 請求中是否包含 Service Ticket ( ST )和 Ticket Granting tieckt(TGT) ,若是沒有,則說明當前用戶還沒有登陸,因而將請求重定向到指定好的 CAS Server 登陸地址,並傳遞 Service (也就是要訪問的目的資源地址),以便登陸成功事後轉回該地址。用戶在第 3 步中輸入認證信息,若是登陸成功, CAS Server 隨機產生一個至關長度、惟1、不可僞造的 Service Ticket ,並緩存以待未來驗證,以後系統自動重定向到 Service 所在地址,併爲客戶端瀏覽器設置一個 Ticket Granted Cookie ( TGC ), CAS Client 在拿到 Service 和新產生的 Ticket 事後,在第 5 , 6 步中與 CAS Server 進行身份覈實,以確保 Service Ticket 的合法性。
2 、在該協議中,全部與 CAS 的交互均採用 SSL 協議確保 ST 和 TGC 的安全性。協議工做過程當中會有 2 次重定向的過程,可是 CAS Client 與 CAS Server 之間進行 Ticket 驗證的過程對於用戶是透明的。
3 、 CAS 如何實現 SSO
當用戶訪問另外一服務再次被重定向到 CAS Server 的時候, CAS Server 會
2、 Spring Security
2.1 如何控制權限
一、 概要
Spring 使用由 Filter 組成的 Chain ,來判斷權限。 Spring 預約義了不少 out-of-boxed filter 供開發者直接使用。每一個 Filter 通常狀況下(有些 Filter 是 abstract 的)都和配置文件的一個元素(有的狀況下多是屬性)對應。好比: AUTHENTICATION_PROCESSING_FILTER ,對應配置文件裏面的: http/form-login 元素。
若是 Spring 提供的 Filter 不能知足系統的權限功能,開發者能夠自定義 Filter ,而後把 Filter 放在某個 Filter Chain 的某個位置。能夠替換掉原有 Filter Chain 的某個 Filter ,也能夠放在某個 Filter 以前或者以後。
總之, Spring Security 採用 Filter Chain 模式判斷權限, Spring 提供了一些 Filter ,也支持開發者自定義 Filter 。
二、 控制內容
Spring Security 提供對 URL 、 Bean Method 、 Http Session 、領域對象這四種內容的控制,分別以下:
1) URL
能夠分爲須要權限判斷的 url ,不須要權限判斷的 url ,登陸表單 url 。須要權限判斷的 url ,僅限於作角色判斷,就是說判斷當前用戶是否具備指定的角色。
2) Bean Method
Spring 支持對 Service layer method 作權限判斷。也僅限於作角色判斷。配置方式有 2 種:
Annotation : 寫在 Java 源代碼裏面( Annotation ),如: @Secured("ROLE_TELLER") (該方法只有具備 TELLER 角色的用戶可以訪問,不然拋出異常);
寫在配置文件裏面,如: <protect method="set*" access="ROLE_ADMIN" /> (該 bean 的全部 set 方法,只有具備 ADMIN 角色的用戶可以訪問,不然拋出異常)。
3) Http Session
控制一個用戶名是否能重複登陸,以及重複登陸次數,並不是重試密碼次數。
4) 領域對象(待完善)
複雜程序經常須要定義訪問權限,不是簡單的 web 請求或方法調用級別。而是,安全決議,須要包括誰(認證),哪裏( MethodInvocation )和什麼(一些領域對象)。換而言之,驗證決議也須要考慮真實的領域對象實例,方法調用的主體。主要經過 ACL (訪問控制列表)實現。
2.2 權限控制的幾種配置方法
在 Spring Security 3 的使用中,權限控制有 4 種配置方法:
一、 所有利用配置文件,將用戶、權限、資源 (url) 硬編碼在 xml 文件中;
二、 用戶和權限用數據庫存儲,而資源 (url) 和權限的對應採用硬編碼配置;
三、 細分角色和權限,並將用戶、角色、權限和資源均採用數據庫存儲,而且自定義過濾器,代替原有的 FilterSecurityInterceptor 過濾器,並分別實現 AccessDecisionManager 、 InvocationSecurityMetadataSourceService 和 UserDetailsService ,並在配置文件中進行相應配置;(將主要考慮該方法)
四、 修改 spring security 的源代碼,主要是修改 InvocationSecurityMetadataSourceService 和 UserDetailsService 兩個類。 前者是將配置文件或數據庫中存儲的資源 (url) 提取出來加工成爲 url 和權限列表的 Map 供 Security 使用,後者提取用戶名和權限組成一個完整的 (UserDetails)User 對象,該對象能夠提供用戶的詳細信息供 AuthentationManager 進行認證與受權使用。比較暴力,不推薦。
2.3 主要內置對象或內容
一、 Spring Security 默認的過濾器順序列表
order |
過濾器名稱 |
備註 |
100 |
ChannelProcessingFilter |
|
200 |
ConcurrentSessionFilter |
|
300 |
SecurityContextPersistenceFilter |
|
400 |
LogoutFilter |
|
500 |
X509AuthenticationFilter |
|
600 |
RequestHeaderAuthenticationFilter |
|
700 |
CasAuthenticationFilter |
|
800 |
UsernamePasswordAuthenticationFilter |
|
900 |
OpenIDAuthenticationFilter |
|
1000 |
DefaultLoginPageGeneratingFilter |
|
1100 |
DigestAuthenticationFilter |
|
1200 |
BasicAuthenticationFilter |
|
1300 |
RequestCacheAwareFilter |
|
1400 |
SecurityContextHolderAwareRequestFilter |
|
1500 |
RememberMeAuthenticationFilter |
|
1600 |
AnonymousAuthenticationFilter |
|
1700 |
SessionManagementFilter |
|
1800 |
ExceptionTranslationFilter |
|
1900 |
FilterSecurityInterceptor |
|
2000 |
SwitchUserFilter |
2.4 主要應用模式
一、 自定義受權管理
自定義 Filter 以及相關輔助類,實現用戶、角色、權限、資源的數據庫管理,涉及相關接口或類說明以下:
1) AbstractSecurityInterceptor
具體實現類做爲過濾器,該過濾器要插入到受權以前。在執行 doFilter 以前,進行權限的檢查,而具體的實現交給 accessDecisionManager 。
2) FilterInvocationSecurityMetadataSource
具體實現類在初始化時,要實現從數據庫或其它存儲庫中加載全部資源與權限(角色),並裝配到 MAP <String, Collection<ConfigAttribute>> 中。 資源一般爲 url , 權限就是那些以 ROLE_ 爲前綴的角色,資源爲 key , 權限爲 value 。 一個資源能夠由多個權限來訪問。
3) UserDetailService
具體實現類從存儲庫中讀取特定用戶的各類信息(用戶的密碼,角色信息,是否鎖定,帳號是否過時等)。惟一要實現的方法: public UserDetails loadUserByUsername(String username)
4) AccessDecisionManager
匹配權限以決定是否放行。主要實現方法:
public void decide (Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes)
//In this method, need to compare authentication with configAttributes.
A object is a URL, a filter was find permission configuration by this URL, and pass to here.
Check authentication has attribute in permission configuration (configAttributes)
If not match corresponding authentication, throw a AccessDeniedException.
3、CAS Client 與 Spring Security 整合
3.1 環境需求
1 、 CAS Client : cas-client-core-3.2.1.jar 放入 Web 應用的 lib 中
2 、 Spring Security : spring-security-cas-client-3.0.2.RELEASE.jar ,在基於 spring security 項目中加入 cas 相應的依賴 jar 包
3.2 搭建 CAS Client (即 Spring Security 應用)
一、 導入服務端生成證書
複製 cas 服務端生成證書 server.cer 到客戶端(TOMCAT_Home下),並將證書導入 JDK 中 ,
keytool -import -trustcacerts -alias casserver -file server.cer -keystore D:\Java\jre1.6.0_02\lib\security\cacerts -storepass changeit
注此處的 jre 必須爲 JDK 路徑下的 jre 。
二、 配置 CAS Client 應用的 web.xml
增長以下:
<!-- spring 配置文件 --> < context-param > < param-name > contextConfigLocation </ param-name > < param-value > classpath:applicationContext.xml,classpath:applicationContext-security.xml </ param-value > </ context-param > <!-- Character Encoding filter --> < filter > < filter-name > encodingFilter </ filter-name > < filter-class > org.springframework.web.filter.CharacterEncodingFilter </ filter-class > < init-param > < param-name > encoding </ param-name > < param-value > UTF-8 </ param-value > </ init-param > </ filter > < filter-mapping > < filter-name > encodingFilter </ filter-name > < url-pattern > /* </ url-pattern > </ filter-mapping > <!-- spring security filter --> <!-- DelegatingFilterProxy 是一個 SpringFramework 的類,它能夠代理一個 applicationcontext 中定義的 Springbean 所實現的 filter --> < 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 默認偵聽器 --> < listener > < listener-class > org.springframework.web.context.ContextLoaderListener </ listener-class > </ listener >
三、 配置 applicationContext-security.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:s="http://www.springframework.org/schema/security" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.4.xsd" default-autowire="byType" default-lazy-init="true"> <!-- 設置相應的切入點與 filter,auto-config="false" 不使用 http 的自動配置 --> <s:http auto-config="false" entry-point-ref="casEntryPoint" servlet-api-provision="true" access-decision-manager-ref="accessDecisionManager"> <s:intercept-url pattern="/getUserRoleList.s" filters="none"/> <s:intercept-url pattern="/editUserRole.s" filters="none"/> <s:intercept-url pattern="/getRoleList.s" filters="none"/> <s:intercept-url pattern="/css/**" filters="none"/> <s:intercept-url pattern="/js/**" filters="none"/> <s:intercept-url pattern="/p_w_picpaths/**" filters="none"/> <s:access-denied-handler ref="cdnAccessDeniedHandler"/> <s:custom-filter ref="securityFilter" before="FILTER_SECURITY_INTERCEPTOR"/> <s:custom-filter position="CAS_FILTER" ref="casFilter"/> <s:custom-filter before="LOGOUT_FILTER" ref="requestSingleLogoutFilter"/> <s:custom-filter before="CAS_FILTER" ref="singleLogoutFilter"/> </s:http> <!-- An access decision manager used by the business objects --> <!-- 被「access-decision-manager-ref」所引用 --> <bean id="accessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased"> <!-- 是否容許全部的投票者棄權,若是爲false,表示若是全部的投票者棄權,就禁止訪問 --> <property name="allowIfAllAbstainDecisions"> <value>false</value> </property> <property name="decisionVoters"> <list> <ref local="roleVoter" /> </list> </property> </bean> <!-- An access decision voter that reads AUTH_* configuration settings --> <!-- RoleVoter默認角色名稱都要以ROLE_開頭,不然不會被計入權限控制,若是要修改前綴,能夠經過對rolePrefix屬性進行修改 --> <bean id="roleVoter" class="org.springframework.security.access.vote.RoleVoter"> <property name="rolePrefix"> <value>AUTH_</value> </property> </bean> <!-- 被「access-denied-handler」所引用 --> <bean id="cdnAccessDeniedHandler" class="com.neil.security.CdnAccessDeniedHandler"> <property name="accessDeniedUrl" value="/accessDeniedRedirect.jsp" /> <property name="logoutUrl" value="/j_spring_cas_security_logout" /> </bean> <bean id="singleLogoutFilter" class="org.jasig.cas.client.session.SingleSignOutFilter" /> <bean id="requestSingleLogoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter" > <constructor-arg value="${casServerRoot}logout?service=${casClientRoot}index.html" /> <constructor-arg> <bean class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler" /> </constructor-arg> <property name="filterProcessesUrl" value="/j_spring_cas_security_logout" /> </bean> <!-- cas切入點,被「entry-point-ref」所引用 --> <bean id="casEntryPoint" class="org.springframework.security.cas.web.CasAuthenticationEntryPoint"> <property name="loginUrl" value="${casServerRoot}login"/> <property name="serviceProperties" ref="serviceProperties"/> </bean> <!-- serviceProperties 爲認證成功後服務端返回的地址 . 該地址將做爲參數傳遞到服務端 , 此處不能寫爲 IP 的形式。需寫爲主機名 ( 證書生成時寫的計算機全名 ) 或域名 --> <bean id="serviceProperties" class="org.springframework.security.cas.ServiceProperties"> <property name="service" value="${casClientRoot}j_spring_cas_security_check"/> <!-- sendRenew 爲 boolean 類型 當爲 true 時每新打開窗口則需從新登陸 --> <property name="sendRenew" value="false"/> </bean> <!-- cas認證提供器,定義客戶端的驗證方式 --> <bean id="casAuthenticationProvider" class="org.springframework.security.cas.authentication.CasAuthenticationProvider"> <!-- 客戶端只驗證用戶名是否合法 --> <property name="authenticationUserDetailsService" ref="casAuthenticationUserDetailsService"/> <property name="serviceProperties" ref="serviceProperties" /> <property name="ticketValidator"> <bean class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator"> <constructor-arg index="0" value="${casServerRoot}" /> </bean> </property> <property name="key" value="an_id_for_this_auth_provider_only"/> </bean> <!-- 認證用戶信息 --> <bean id="casAuthenticationUserDetailsService" class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper"> <property name="userDetailsService" > <ref local="userDetailsService"/> </property> </bean> <!-- 用戶信息操做服務 --> <bean id="userDetailsService" class="com.neil.security.dao.impl.LogonIdAuthorityDaoImpl"> <property name="sessionFactory" ref="sessionFactorySecurity" /> </bean> <!-- 自定義的"CAS_FILTER"(cas 認證過濾器 ) --> <!-- begin --> <bean id="casFilter" class="org.springframework.security.cas.web.CasAuthenticationFilter"> <property name="authenticationManager" ref="authenticationManager"/> </bean> <!-- 在認證管理器中註冊cas認證提供器 --> <s:authentication-manager alias="authenticationManager"> <s:authentication-provider ref="casAuthenticationProvider"/> </s:authentication-manager> <!-- end --> <!-- 自定義的Security FILTER --> <!-- begin --> <bean id="securityFilter" class="com.neil.security.FilterSecurityInterceptor"> <property name="authenticationManager" ref="authenticationManager" /> <property name="accessDecisionManager" ref="cdnAccessDecisionManager" /> <property name="securityMetadataSource" ref="securityMetadataSource"/> </bean> <bean id="securityMetadataSource" class="com.neil.security.InvocationSecurityMetadataSource" /> <bean id="cdnAccessDecisionManager" class="com.neil.security.CdnDecisionManager" /> <!-- end --> </beans>
PS:
一、以上配置文件中${XXX}是替換屬性,由於在本人的工程中有相關配置關鍵存儲了該屬性的值,故在該xml中用佔位符替換。其實該位置也能夠用hardcode的url替換。如,http://localhost:9001/client/等。
二、如下連接的內容是CasAuthenticationProvider.java的源代碼:
http://code.taobao.org/p/openclouddb/diff/294/trunk/MyCat-web/rainbow-web/src/main/java/org/springframework/security/cas/authentication/CasAuthenticationProvider.java
4、代碼實現
1,自定義的Security FILTER---FilterSecurityInterceptor.java:
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter { private FilterInvocationSecurityMetadataSource securityMetadataSource; 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 { //在「beforeInvocation」該方法中, //一、會調用ConfigAttributeDefinition attr = this.obtainObjectDefinitionSource().getAttributes(object); 來獲取訪問該url的權限;ps:object就是fi。 //二、會調用Authentication authenticated = authenticateIfRequired();來獲取該用戶擁有的權限。 //三、調用accessDecisionManager的decide(authenticated, object, attr);來決定該用戶有沒有訪問該url的權限。若是有,則繼續;若是沒有則拋出異常。 //四、調用Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attr);去嘗試run as a different user。 // 若是不爲空,則把它放入上下文中: SecurityContextHolder.getContext().setAuthentication(runAs); //五、返回token InterceptorStatusToken token = super.beforeInvocation(fi); try { 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; } @Override public void destroy() { } @Override public void init(FilterConfig arg0) throws ServletException { } }
doFilter是AbstractSecurityInterceptor最主要實現的方法,該方法完成了驗證流程。而beforeInvocation是被調用的最重要的方法,它纔是真正完成驗證流程的方法。下面貼出該方法的源碼,解釋說明在上面的代碼註釋中有描述:
protected InterceptorStatusToken beforeInvocation(Object object) { Assert.notNull(object, "Object was null"); if (!getSecureObjectClass().isAssignableFrom(object.getClass())) { throw new IllegalArgumentException("Security invocation attempted for object " + object.getClass().getName() + " but AbstractSecurityInterceptor only configured to support secure objects of type: " + getSecureObjectClass()); } ConfigAttributeDefinition attr = this.obtainObjectDefinitionSource().getAttributes(object); if (attr == null) { if (rejectPublicInvocations) { throw new IllegalArgumentException( "No public invocations are allowed via this AbstractSecurityInterceptor. " + "This indicates a configuration error because the " + "AbstractSecurityInterceptor.rejectPublicInvocations property is set to 'true'"); } if (logger.isDebugEnabled()) { logger.debug("Public object - authentication not attempted"); } publishEvent(new PublicInvocationEvent(object)); return null; // no further work post-invocation } if (logger.isDebugEnabled()) { logger.debug("Secure object: " + object + "; ConfigAttributes: " + attr); } if (SecurityContextHolder.getContext().getAuthentication() == null) { credentialsNotFound(messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound", "An Authentication object was not found in the SecurityContext"), object, attr); } Authentication authenticated = authenticateIfRequired(); // Attempt authorization try { this.accessDecisionManager.decide(authenticated, object, attr); } catch (AccessDeniedException accessDeniedException) { AuthorizationFailureEvent event = new AuthorizationFailureEvent(object, attr, authenticated, accessDeniedException); publishEvent(event); throw accessDeniedException; } if (logger.isDebugEnabled()) { logger.debug("Authorization successful"); } AuthorizedEvent event = new AuthorizedEvent(object, attr, authenticated); publishEvent(event); // Attempt to run as a different user Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attr); if (runAs == null) { if (logger.isDebugEnabled()) { logger.debug("RunAsManager did not change Authentication object"); } // no further work post-invocation return new InterceptorStatusToken(authenticated, false, attr, object); } else { if (logger.isDebugEnabled()) { logger.debug("Switching to RunAs Authentication: " + runAs); } SecurityContextHolder.getContext().setAuthentication(runAs); // revert to token.Authenticated post-invocation return new InterceptorStatusToken(authenticated, true, attr, object); } } /** * Checks the current authentication token and passes it to the AuthenticationManager if * {@link org.springframework.security.Authentication#isAuthenticated()} returns false or the property * <tt>alwaysReauthenticate</tt> has been set to true. * * @return an authenticated <tt>Authentication</tt> object. */ private Authentication authenticateIfRequired() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication.isAuthenticated() && !alwaysReauthenticate) { if (logger.isDebugEnabled()) { logger.debug("Previously Authenticated: " + authentication); } return authentication; }
2,獲取url訪問的所須要權限---InvocationSecurityMetadataSource.java:
public class InvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { static Logger logger = Logger.getLogger(InvocationSecurityMetadataSource.class); @Autowired private IModuleAuthoritieService moduleAuthService; private UrlMatcher urlMatcher = new AntUrlPathMatcher();; public static Map<String, List<ConfigAttribute>> resourceMap = null; public InvocationSecurityMetadataSource() { // loadResourceDefine(); } private void loadResourceDefine() { resourceMap = new HashMap<String, List<ConfigAttribute>>(); Map<String, List<String>> hash = moduleAuthService.getAllModuleAuthRef(); for(String url : hash.keySet()) { List<ConfigAttribute> list = new ArrayList<ConfigAttribute>(); List<String> authList = hash.get(url); for(String auth : authList) { list.add(new SecurityConfig(auth)); } resourceMap.put(url, list); } } // According to a URL, Find out permission configuration of this URL. @Override public List<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { if(resourceMap == null) { loadResourceDefine(); } // guess object is a URL. String url = ((FilterInvocation) object).getRequestUrl(); if(url.indexOf("?") != -1) { url = url.substring(0, url.indexOf("?")); } List<ConfigAttribute> result = null; Iterator<String> ite = resourceMap.keySet().iterator(); while (ite.hasNext()) { String resURL = ite.next(); if (urlMatcher.pathMatchesUrl(url, resURL)) { result = resourceMap.get(resURL); logger.info("resURL : " + resURL + " , result : " + result); return result; } } return null; } public boolean supports(Class<?> clazz) { return true; } public Collection<ConfigAttribute> getAllConfigAttributes() { return null; } }
該類主要是要實現List<ConfigAttribute> getAttributes(Object object)來回去url的權限。
3,決策管理,決定某個資源某用戶是否有權限操做---AccessDecisionManager(CdnDecisionManager.java):
public class CdnDecisionManager implements AccessDecisionManager { static Logger logger = Logger.getLogger(CdnDecisionManager.class); 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 needAuth = ((SecurityConfig) ca).getAttribute(); for (GrantedAuthority ga : authentication.getAuthorities()) { if (needAuth.equals(ga.getAuthority())) { // ga is user's role. logger.info("match Auth : " + needAuth); return; } } } throw new AccessDeniedException("您無權訪問此頁面"); } public boolean supports(ConfigAttribute attribute) { return true; } public boolean supports(Class<?> clazz) { return true; } }
decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)是它須要實現的最重要的方法。該方法決定某個資源(object)某用戶(authentication--用戶信息及其擁有的權限)是否有權限操做。
四、用戶信息操做服務,獲取用戶所擁有的權限信息:
<!-- 用戶信息操做服務 --> <bean id="userDetailsService" class="com.neil.security.dao.impl.LogonIdAuthorityDaoImpl"> <property name="sessionFactory" ref="sessionFactorySecurity" /> </bean>
由於實現方式不同,這裏就補貼出詳細代碼了。總之,該類最主要的實現的方法是UserDetails loadUserByUsername(String logonId),經過用戶名信息去獲取UserDetails對象,該對象包括用戶名和該用戶的權限。
5、參考資料
最主要的參考資料是:
http://fansofjava.iteye.com/blog/593624
http://www.coin163.com/java/docs/201305/d_2785029006.html
上面兩篇文章講的比較詳細,特做爲參考。