Spring Security---CAS实现单点登陆

    最近由于一直在用的一个系统,在登陆是总是出现某个问题,而这个系统是用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 最基础协议:

wKiom1RB9afBhg3zAAEjzQ3yPjY536.jpg

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

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

相关文章
相关标签/搜索