使用 Acegi Security 集成 CAS

一、背景

公司早期(2005年)开发的一系统S,现欲使用公司2009年左右上线的用户管理系统UM来实现用户单点登录。

技术架构现状:

系统S:Spring 1.2.5、webwork 2.1.7、xwork 1.0.5、Hibernate 3.0.5

用户管理系统UM:基于 CAS Server 3.2.1

二、集成方式

初步考虑有如下两种集成方式:

1、使用 Acegi Security

优点:无 Spring 版本兼容性问题

缺点:无使用经验;时间久远,相关技术资源可能较难寻。

2、使用 Spring Security 2.x

优点:有使用Spring Security 3.x 集成 CAS 的经验;Spring、Hibernate等开源组件的升级,可对系统未来的开发引入新的技术特性。

缺点:需 升级 Spring,可能因升级带来 Spring、Hibernate 等开源组件的兼容性问题。

后来我们对上述两种方式均进行了尝试并取得成功,下面仅就方式1进行说明。

三、集成过程

先前无使用 Acegi Security 的经验,但借助Google参阅了官方参考文档和网络示例,在理解了各个组件的用途后,集成过程还是比较简单的。

整个过程主要是引入所依赖的jar和维护2个配置文件。

1、引入需要的依赖

acegi-security-1.0.7.jar

acegi-security-cas-1.0.7.jar

casclient-2.1.0.jar

2、修改web.xml

Acegi Security新增一个过滤器(filter),配置代码如下:

    <filter>
    	<filter-name>acegiSecurityFilter</filter-name>
    	<filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class>
    	<init-param>
    		<param-name>targetClass</param-name>
    		<param-value>org.acegisecurity.util.FilterChainProxy</param-value>
    	</init-param>
    </filter>
    <filter-mapping>
	<filter-name>acegiSecurityFilter</filter-name>
	<url-pattern>/*</url-pattern>
    </filter-mapping>

3、新增acegi-security-context.xml

文件中配置的是 Acegi Security 及集成CAS相关的 bean。具体bean的配置代码如下:

    <!-- 
        过滤器链,这是我们在web.xml新增的 acegiSecurityFilter 所需的FilterChainProxy的实例。
        注意:这里过滤器的顺序很重要!
     -->
	<bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
		<property name="filterInvocationDefinitionSource">
			<value>
				CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
				PATTERN_TYPE_APACHE_ANT
				/**=httpSessionContextIntegrationFilter,anonymousProcessingFilter,casProcessingFilter,exceptionTranslationFilter,filterSecurityInterceptor,logoutFilter
			</value>
		</property>
	</bean>
    <!-- 
       为HTTP请求,保存SecurityContext至HttpSession中。
     -->
	<bean id="httpSessionContextIntegrationFilter" class="org.acegisecurity.context.HttpSessionContextIntegrationFilter">
	</bean>

    <!-- 
       处理匿名访问
     -->
    <bean id="anonymousProcessingFilter" class="org.acegisecurity.providers.anonymous.AnonymousProcessingFilter">
        <property name="key" value="foobar" />
        <property name="userAttribute" value="anonymousUser,ROLE_ANONYMOUS" />
    </bean>

    <!-- 
       处理HTTP资源安全访问
     -->
	<bean id="filterSecurityInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
		<property name="authenticationManager">
			<ref bean="authenticationManager" />
		</property>
		<property name="accessDecisionManager">
			<ref bean="accessDecisionManager" />
		</property>
		<property name="objectDefinitionSource">
			<value>
			    CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
				PATTERN_TYPE_APACHE_ANT
                /casfailed.jsp=ROLE_ANONYMOUS
                /acegi_login.jsp=ROLE_ANONYMOUS,ROLE_USER
				/**/*.action=ROLE_USER
				/**/*.jsp=ROLE_USER
			</value>
		</property>
	</bean>
	<bean id="accessDecisionManager" class="org.acegisecurity.vote.UnanimousBased">
		<property name="decisionVoters">
			<list>
				<ref bean="roleVoter"/>
			</list>
		</property>
	</bean>
	<bean id="roleVoter" class="org.acegisecurity.vote.RoleVoter">
	   <property name="rolePrefix">
	       <value>ROLE_</value>
	   </property>
	</bean>
<bean id="casProcessingFilter" class="org.acegisecurity.ui.cas.CasProcessingFilter">
		<property name="authenticationManager">
			<ref bean="authenticationManager" />
		</property>
		<property name="authenticationFailureUrl">
			<value>/casfailed.jsp</value>
		</property>
		<property name="defaultTargetUrl">
			<value>/</value>
		</property>
        <property name="alwaysUseDefaultTargetUrl">
            <value>false</value>
        </property>
		<property name="filterProcessesUrl">
			<value>/j_security_check</value>
		</property>
	</bean>

	<bean id="logoutFilter" class="org.acegisecurity.ui.logout.LogoutFilter">
		<constructor-arg value="${ulic.cas.server}/logout?url=${app.url}" />
		<constructor-arg>
			<list>
				<bean class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler" />
			</list>
		</constructor-arg>
		<property name="filterProcessesUrl">
			<value>/j_security_logout</value>
		</property>
	</bean>
	<bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter">
		<property name="authenticationEntryPoint">
			<ref local="casProcessingFilterEntryPoint" />
		</property>
	</bean>
	<bean id="casProcessingFilterEntryPoint" class="org.acegisecurity.ui.cas.CasProcessingFilterEntryPoint">
		<property name="serviceProperties">
			<ref bean="serviceProperties" />
		</property>
		<property name="loginUrl">
			<value>${ulic.cas.server}/login</value>
		</property>
	</bean>
	<bean id="serviceProperties" class="org.acegisecurity.ui.cas.ServiceProperties">
        <property name="service">
            <value>${app.url}/j_security_check</value>
        </property>
        <property name="sendRenew">
            <value>false</value>
        </property>
    </bean>
	<bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
		<property name="providers">
			<list>
				<ref bean="casAuthenticationProvider" />
				<ref bean="anonymousAuthenticationProvider"/>
			</list>
		</property>
	</bean>
	<bean id="casAuthenticationProvider" class="org.acegisecurity.providers.cas.CasAuthenticationProvider">
		<property name="casAuthoritiesPopulator">
			<ref bean="casAuthoritiesPopulator" />
		</property>
		<property name="casProxyDecider">
			<bean class="org.acegisecurity.providers.cas.proxy.RejectProxyTickets" />
		</property>
		<property name="ticketValidator">
		    <ref bean="casProxyTicketValidator" />
		</property>
		<property name="key">
			<value>my_password_for_this_auth_provider_only</value>
		</property>
	</bean>
	<bean id="anonymousAuthenticationProvider" class="org.acegisecurity.providers.anonymous.AnonymousAuthenticationProvider">
		<property name="key">
			<value>foobar</value>
		</property>
	</bean>

	<bean id="casProxyTicketValidator"
		class="org.acegisecurity.providers.cas.ticketvalidator.CasProxyTicketValidator">
		<property name="casValidate">
			<value>${ulic.cas.server}/proxyValidate</value>
		</property>
		<property name="serviceProperties">
			<ref local="serviceProperties" />
		</property>
	</bean>

    <bean id="casAuthoritiesPopulator" class="org.acegisecurity.providers.cas.populator.DaoCasAuthoritiesPopulator">
		<property name="userDetailsService">
			<ref bean="jdbcDaoImpl" />
		</property>
    </bean>

    <bean id="jdbcDaoImpl" class="org.acegisecurity.userdetails.jdbc.JdbcDaoImpl">
        <property name="dataSource" ref="dataSource"/>
        <property name="usersByUsernameQuery">
            <value>SELECT t.user_name AS username, 'nopassword' AS password, 1 AS enabled FROM t_user t WHERE t.user_name = ?</value>
        </property>
        <property name="authoritiesByUsernameQuery">
            <value>SELECT t.user_name AS username, 'ROLE_USER' AS authority FROM t_user t WHERE t.user_name = ?</value>
        </property>
    </bean>

四、问题及解决

casProxyTicketValidator

casProxyTicketValidator 的属性 casValidate 必须为一个 https 协议的地址,但公司的 CAS Server 并未配置成 https 协议,使用的是 http 协议,这样直接导致CAS验证失败。

通过覆写casclient-2.1.0.jar中的类edu.yale.its.tp.cas.util.SecureURL,取消对 https 协议的检查。

    /** 
     * Retrieve the contents from the given URL as a String, assuming the
     * URL's server matches what we expect it to match.
     */
    public static String retrieve(String url) throws IOException {
    	if (log.isTraceEnabled()){
    		log.trace("entering retrieve(" + url + ")");
    	}
        BufferedReader r = null;
        try {
            URL u = new URL(url);
//            if (!u.getProtocol().equals("https")){
//            	// IOException may not be the best exception we could throw here
//            	// since the problem is with the URL argument we were passed, not
//            	// IO. -awp9
//            	log.error("retrieve(" + url + ") on an illegal URL since protocol was not https.");
//							throw new IOException("only 'https' URLs are valid for this method");
//            }
                
            URLConnection uc = u.openConnection();
            uc.setRequestProperty("Connection", "close");
            r = new BufferedReader(new InputStreamReader(uc.getInputStream()));
            String line;
            StringBuffer buf = new StringBuffer();
            while ((line = r.readLine()) != null)
                buf.append(line + "\n");
            return buf.toString();
        } finally {
            try {
                if (r != null)
                    r.close();
            } catch (IOException ex) {
                // ignore
            }
        }
    }

五、资源

acegisecurity-1.0.7

CAS Java Client

acegisecurity-reference-1.0.7.pdf

Acegi Security

Using CAS with Acegi


猜你喜欢

转载自blog.csdn.net/npstar/article/details/26267607