实现Web应用的单一登录、单一退出

http://forum.springside.org.cn/viewthread.php?tid=3281

平台:
Windows
JDK 1.5
Tomcat 5.5.20
ApacheDS 1.0.2
Spring Framework 2.5.5
Spring Security 2.0.3
CAS Server 3.3
Java CAS Client 3.1.3

配置过程
1 安装JDK,Tomcat,ApacheDS
一般来说,JDK,tomcat均采用zip包,拷贝后设置系统环境变量,不赘述。
ApacheDS安装完成后,缺省的LDAP端口采用的是10389,到其安装目录下conf子目录中找到server.xml,可以修改为标准的389端口,便于后面的测试。
另外,需要注意的是,为了便于管理,最好安装一个LDAP管理客户端,推荐LDAP Browser.

2 安装CAS Server
直接将下载来的cas server解包,在modules子目录下面有一个cas-server-webapp-3.3.war的文件,用压缩软件将其打开,解压到cas目录中,

拷贝到tomcat的webapps目录之下。

2.1  让CAS Server使用LDAP目录,下面的配置实用了刚刚本地安装的ApacheDS,端口没有明确指定,说明使用缺省端口
修改authenticationHandlers为下面的样子
<property name="authenticationHandlers">
    <list>
        <bean class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler"     
                p:httpClient-ref="httpClient" />
        <bean    class="org.jasig.cas.adaptors.ldap.BindLdapAuthenticationHandler">
            <property name="filter" value="uid=%u" />
            <property name="searchBase" value="ou=system" />
            <property name="contextSource" ref="contextSource" />
        </bean>
    </list>
</property>


添加LDAP contextSource的bean定义
<bean id="contextSource" class="org.jasig.cas.adaptors.ldap.util.AuthenticatedLdapContextSource">
    <property name="urls">
        <list>
            <value>ldap://localhost/</value>
        </list>
    </property>
    <property name="userName" value="uid=admin,ou=system" />
    <property name="password" value="secret" />
    <property name="baseEnvironmentProperties">
        <map>
            <entry>
                <key>
                    <value>
                        java.naming.security.authentication
                    </value>
                </key>
                <value>simple</value>
            </entry>
        </map>
    </property>
</bean>


2.2 配置Tomcat 5.5.20的SSL
首先,需要创建证书,并指定Tomcat使用该证书,然后将证书导入到JRE环境
该过程可以使用keytool工具完成,我将其过程编写为一个批处理文件,直接使用即可
keytool -delete -alias tomcat -keystore D:/java/jdk1.5.0_07/jre/lib/security/cacerts -storepass changeit

del server.keystore
keytool -genkey -alias tomcat -keyalg RSA -keypass password -storepass password -keystore server.keystore -validity 3600
del server.cer
keytool -export -trustcacerts -alias tomcat -file server.cer -keystore server.keystore -storepass password

keytool -import -trustcacerts -alias tomcat -file server.cer -keystore  D:/java/jdk1.5.0_07/jre/lib/security/cacerts -storepass changeit


读者在使用过程中,需要根据自己的情况修改。比如第一行的删除证书,是便于重新生成证书,再创建添加的,如果首次使用,这行则需要屏蔽掉。另外,如果密码和jdk目录不同,也请修改。

【注意】在创建证书时,当系统提示你让输入姓名的时候,一定要填写要部署的主机名,Spring Security会在验证用户ticket的时候出现异常,提示类似"host name must be localhost"之类的信息。

好了,证书创建导入完毕,可以在tomcat中启用,注意不同的tomcat也许配置有细微差别,请注意tomcat的server.xml文档中的说明。
在5.5.20中,我们配置如下
<Connector acceptCount="100" clientAuth="false" disableUploadTimeout="true" enableLookups="false" 
keystoreFile="d:/java/apache-tomcat-5.5.20/server.keystore" keystorePass="password" maxHttpHeaderSize="8192" 
maxSpareThreads="75" maxThreads="150" minSpareThreads="25" port="8443" protocol="org.apache.coyote.http11.Http11Protocol" 
scheme="https" secure="true" sslProtocol="TLS"/>


2.3 测试CAS Server
启动Tomcat,在浏览器中输入https://localhost:8444/cas
浏览器中将显示cas服务器的登录路径,输入ApacheDS的缺省管理员帐号admin,密码secret,如果提示登录成功,则说明配置无误。

2.4 配置Speing security 2.0.3
配置Spring中关于安全的Bean,代码如下

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:sec="http://www.springframework.org/schema/security"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
                        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring

-security-2.0.xsd"
    default-autowire="byName" default-lazy-init="true">
    
    <sec:http entry-point-ref="casProcessingFilterEntryPoint">
        <sec:intercept-url pattern="/index.jsp" access="ROLE_USER" />
        <sec:intercept-url pattern="/admin/**" access="ROLE_USER" />
        <sec:intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY" />
        <sec:logout logout-success-url="/cas-logout.jsp"/>
        <sec:anonymous />
       
    </sec:http>
    
    <sec:authentication-manager alias="authenticationManager"/>

    <bean id="casProcessingFilter" class="org.springframework.security.ui.cas.CasProcessingFilter">
        <sec:custom-filter after="CAS_PROCESSING_FILTER"/>
        <property name="authenticationManager" ref="authenticationManager"/>
        <property name="authenticationFailureUrl" value="/casfailed.jsp"/>
        <property name="defaultTargetUrl" value="/"/>
    </bean>

    <bean id="casProcessingFilterEntryPoint" class="org.springframework.security.ui.cas.CasProcessingFilterEntryPoint">
        <property name="loginUrl" value="https://localhost:8443/cas/login"/>
        <property name="serviceProperties" ref="serviceProperties"/>
    </bean>

    <bean id="casAuthenticationProvider" class="org.springframework.security.providers.cas.CasAuthenticationProvider">
    <sec:custom-authentication-provider />
    <property name="userDetailsService" ref="userDetailsService"/>
    <property name="serviceProperties" ref="serviceProperties" />
    <property name="ticketValidator">
        <bean class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator">
        <constructor-arg index="0" value="https://localhost:8443/cas" />
       </bean>
    </property>
    <property name="key" value="an_id_for_this_auth_provider_only"/>
    </bean> 

    <bean id="userDetailsService" class="com.hm.org.service.UserDetailServiceImpl" />
    
    <bean id="serviceProperties" class="org.springframework.security.ui.cas.ServiceProperties">
        <property name="service" value="http://localhost/cis2/j_spring_cas_security_check"/>
        <property name="sendRenew" value="false"/>
    </bean>
    
</beans>


需要说明的是<bean id="userDetailsService" class="com.hm.org.service.UserDetailServiceImpl" />这个bean,这个bean实现了UserDetailService接口,即实现了public UserDetails loadUserByUsername(String userName)方法,由于我们只是使用CAS Server进行身份认证,而关于用户的其他信息,还是存储在类似关系数据库这种系统中,因此,我们需要实现自己的 userDetailsService,很简单,这里不列出代码。

该配置中几点需要说明,serviceProperties bean中,service属性,有作为回调实现Single Sign Out的作用。sendRenew属性如果为true要求目标应用一定要显式登录,而不接受SSO,也就是说即使登录了CAS,当前应用也不承认,需要你再次提交用户凭证信息,这对于保护特殊的应用有很大作用,比如用户修改口令等私密信息,即可以采取这种措施。

2.5 配置Single Sign Out
即从任何一点退出系统,则所有的应用应该全部退出。目前版本的CAS服务器支持了该项功能,但Spring Security还不能很好支持(我查阅了Spring Seurity中关于该功能的内容,Scott看上去不太理解哪里没有完成,他认为可以很好支持CAS的Single Sign Out,但实际却是存在一定问题。)

当前版本的Spring security,2.0.3,有单一退出支持,不过我的测试是不成功。具体配置方法为
在web.xml中添加如下的代码
<filter>
   <filter-name>CAS Single Sign Out Filter</filter-name>
   <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>
</filter>

<filter-mapping>
   <filter-name>CAS Single Sign Out Filter</filter-name>
   <url-pattern>/*</url-pattern>
</filter-mapping>

<listener>
    <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
</listener>


2.6 测试SSO
这样,所有配置均已经完成,测试SSO,发现登录任何一个应用,则会定向到CAS服务器的登录界面,然后访问其他应用,不需要再次登录。然而,遗憾的是,但推出CAS服务器时,应用并没有退出,其登录信息已经保存在各自的Session中,虽然我们看到CAS在退出时,向目标service发出了退出消息(打开CAS Server的相关日志,设置为debug可以看到),但Spring securiy没有反应。请参考http://www.ja-sig.org/wiki/display/CASUM/Single+Sign+Out查看CAS的具体行为。

3 一点说明
关于本文的叙述,也可能存在谬误,特别是关于Single Sign Out为何不成功的原因,只是个人的判断。也许是配置不当。

4 附录
4.1 CAS登录和验证不使用ssl通道的方法
在cas-servelet.xml中,将ticketGrantingTicketCookieGenerator和warnCookieGenerator中的cookieSecure属性置为false

4.2 如何在CAS服务器端关闭single sign out(CAS3.2+)
修改WEB-INF/spring-configuration/argumentConfiguration.xml
    
<bean
         id="casArgumentExtractor"
         class="org.jasig.cas.web.support.CasArgumentExtractor">
         <property name="disableSingleSignOut">
          <value>true</value>
         </property>
    </bean>




----------------------------------------
回复本贴是,2.0.4已经出来一段时间了,大家看看这个版本SingleSignOut是否可以成功。
另外,下面是一段jira上关于这个问题的回帖,有参考价值
I think the confusion is how to add the logout filter to the XML configuration file. I think Martino thinks the current filter/listener combo isn't real because there aren't any examples of how to set it up with Spring Security on the CAS website. The real issue is that you need to have the logout filter come before the CAS filter in the security.xml or before the filter chain invocation in the web.xml. The way I do it is in the security.xml.

Fine grained config:
<bean id="securityFilter" class="org.springframework.security.util.FilterChainProxy">
<sec:filter-chain pattern="/**" filters="channelProcessingFilter,httpSessionContextIntegrationFilter,logoutFilter,casSingleSignOutFilter,casProcessingFilter,securityContextHolderAwareRequestFilter,exceptionTranslationFilter,filterInvocationInterceptor"/>
</bean>
<bean id="casSingleSignOutFilter" class="org.jasig.cas.client.session.SingleSignOutFilter"/>

Default config:
<bean id="casSingleSignOutFilter" class="org.jasig.cas.client.session.SingleSignOutFilter">
    <sec:custom-filter before="CAS_PROCESSING_FILTER"/>
</bean>

Then you also need to add the listener to your web.xml. The listener maps the service tickets to the HttpSession.

Hope this helps.

-Matt



----本人还没有校验这篇文章,呵呵

你可能感兴趣的:(spring,tomcat,应用服务器,Web,Security)