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

上面两篇文章讲的比较详细,特作为参考。
 

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