Spring Security---CAS實現單點登錄

    最近因爲一直在用的一個系統,在登錄是老是出現某個問題,而這個系統是用CAS實現的單點登錄。於是,就又回去重新瞭解一邊CAS的認證流程,以及在Spring Security上的實現。這次回顧,讓我對整個流程有了更深入的理解。在這裏特意做一個總結和記錄。

    本人的另一篇講述Spring Security3.1配置的文章,描述的也非常詳細。讀者可結合兩篇文章一起學習。文章地址:http://flyingsnail.blog.51cto.com/5341669/1317752

    第一、二部分的資料來自於網絡,在此作爲參考。

一、       CAS 簡介

1.1   CAS 是什麼

CAS (Central Authentication Service)   Yale 大學發起的一個 Java 開源項目,旨在爲 Web 應用系統提供一種可靠的 單點登錄 解決方案( Web SSO ), CAS 2004 12 月正式成爲 JA-SIG 的一個項目。 CAS 具有以下特點:

    1、   開源的企業級單點登錄解決方案;

    2、   CAS Server 爲需要獨立部署的 Web 應用;

    3、   CAS Client 支持非常多的客戶端 ( 指單點登錄系統中的各個 Web 應用 ) ,包括 Java, .Net, PHP, Perl, 等。

1.2   CAS 原理

從結構上看, CAS 包含兩個部分: CAS Server CAS Client

CAS Server 需要獨立部署,主要負責對用戶的認證工作, 它會處理用戶名 / 密碼等憑證 (Credentials)

CAS Client 部署在客戶端, 負責處理 對本地 Web 應用(客戶端)受保護資源的訪問請求,並且當需要對請求方進行身份認證時,重定向到 CAS Server 進行認證, 下圖是 CAS 最基礎協議:

wKiom1RB9afBhg3zAAEjzQ3yPjY536.jpg

1 CAS Client 與受保護的客戶端應用部署在一起,以 Filter 方式保護受保護的資源。對於訪問受保護資源的每個 Web 請求, CAS Client 會分析該請求的 Http 請求中是否包含 Service Ticket ST )和 Ticket Granting tieckt(TGT) ,如果沒有,則說明當前用戶尚未登錄,於是將請求重定向到指定好的 CAS Server 登錄地址,並傳遞 Service (也就是要訪問的目的資源地址),以便登錄成功過後轉回該地址。用戶在第 3 步中輸入認證信息,如果登錄成功, CAS Server 隨機產生一個相當長度、唯一、不可僞造的 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

二、       Spring Security

2.1 如何控制權限

    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

    2、 控制內容

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 種配置方法:

1、 全部利用配置文件,將用戶、權限、資源 (url) 硬編碼在 xml 文件中;

2、 用戶和權限用數據庫存儲,而資源 (url) 和權限的對應採用硬編碼配置;

3、 細分角色和權限,並將用戶、角色、權限和資源均採用數據庫存儲,並且自定義過濾器,代替原有的 FilterSecurityInterceptor 過濾器,並分別實現 AccessDecisionManager InvocationSecurityMetadataSourceService UserDetailsService ,並在配置文件中進行相應配置;(將主要考慮該方法)

4、 修改 spring security 的源代碼,主要是修改 InvocationSecurityMetadataSourceService UserDetailsService 兩個類。 前者是將配置文件或數據庫中存儲的資源 (url) 提取出來加工成爲 url 和權限列表的 Map Security 使用,後者提取用戶名和權限組成一個完整的 (UserDetails)User 對象,該對象可以提供用戶的詳細信息供 AuthentationManager 進行認證與授權使用。比較暴力,不推薦。

 

2.3 主要內置對象或內容

        

1、 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 主要應用模式

1、 自定義授權管理

自定義 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.

三、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 應用)

    1、 導入服務端生成證書

複製 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 。


    2、 配置 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 >


3、 配置 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="/images/**" 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:

1、以上配置文件中${XXX}是替換屬性,因爲在本人的工程中有相關配置關鍵存儲了該屬性的值,故在該xml中用佔位符替換。其實該位置也可以用hardcode的url替換。如,http://localhost:9001/client/等。

2、以下鏈接的內容是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

四、代碼實現

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”該方法中,
        //1、會調用ConfigAttributeDefinition attr = this.obtainObjectDefinitionSource().getAttributes(object); 來獲取訪問該url的權限;ps:object就是fi。
        //2、會調用Authentication authenticated = authenticateIfRequired();來獲取該用戶擁有的權限。
        //3、調用accessDecisionManager的decide(authenticated, object, attr);來決定該用戶有沒有訪問該url的權限。如果有,則繼續;如果沒有則拋出異常。
        //4、調用Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attr);去嘗試run as a different user。
        //    如果不爲空,則把它放入上下文中: SecurityContextHolder.getContext().setAuthentication(runAs); 
        //5、返回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--用戶信息及其擁有的權限)是否有權限操作。

4、用戶信息操作服務,獲取用戶所擁有的權限信息:

    <!-- 用戶信息操作服務 -->
    <bean id="userDetailsService" class="com.neil.security.dao.impl.LogonIdAuthorityDaoImpl">
        <property name="sessionFactory" ref="sessionFactorySecurity" />
    </bean>

    因爲實現方式不一樣,這裏就補貼出詳細代碼了。總之,該類最主要的實現的方法是UserDetails loadUserByUsername(String logonId),通過用戶名信息去獲取UserDetails對象,該對象包括用戶名和該用戶的權限。


五、參考資料

最主要的參考資料是:

http://fansofjava.iteye.com/blog/593624

http://www.coin163.com/java/docs/201305/d_2785029006.html

上面兩篇文章講的比較詳細,特作爲參考。
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章