公司最近一项目中由于分为多个子系统进行开发和部署,为了是各个系统能够串联工作,故采用了单点登录。参考了很多单点登录的demo,最后采用了CAS的单点登录系统。
一、CAS简介(摘抄自网络)
CAS是CentralAuthentication Service即中央认证服务的简称,它是由耶鲁大学发起的开源项目,其目的是为Web系统提供可靠的单点登录机制。2004年12月CAS正式成为JASIG(jasig.org)的一个项目。
CAS的Server需要作为独立Web应用部署,并且当前Server仅支持Java,但其Client端则支持Java、.Net、PHP、Perl、Apache、uPortal、Ruby等多种开发语言。
各版本的CAS可以分别从以下地址下载。
CAS Server的下载地址:http://downloads.jasig.org/cas/
CAS Client的下载地址:http://downloads.jasig.org/cas-clients/
本人使用的服务端为:Cas server 3.5.2.war,客户端为:cas-client-core-3.2.1.jar
二、cas服务端基本部署
把Cas server 3.5.2.war放到tomcat7的webapps下面,并修改为cas.war。启动tomcat后得到解压缩文件夹cas。
可以在测试服务器的浏览器中输入:http://localhost:8080/cas进行查看是否部署成功。如果出现cas的登录界面说明部署成功。
三、服务端相关配置
1、证书配置:网络上有很多例子可以参考。
2、普通http而非https:
系统目前开发阶段不采用HTTPS进行证书验证,故为了能在开发的时候能够进行,使用HTTP模式。故需要修改配置文件如下:
deployerConfigContext.xml修改如下:
找到<beanclass="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler" p:httpClient-ref="httpClient"/>在其中添加 p:requireSecure="false"。
warnCookieGenerator.xml
Xml代码
<beanid="warnCookieGenerator" class="org.jasig.cas.web.support.CookieRetrievingCookieGenerator"p:cookieSecure="true" p:cookieMaxAge="-1" p:cookieName="CASPRIVACY" p:cookiePath="/cas" />
<beanid="warnCookieGenerator"class="org.jasig.cas.web.support.CookieRetrievingCookieGenerator"
p:cookieSecure="true"
p:cookieMaxAge="-1"
p:cookieName="CASPRIVACY"
p:cookiePath="/cas"/>
ticketGrantingTicketCookieGenerator.xml
Xml代码
<beanid="ticketGrantingTicketCookieGenerator" class="org.jasig.cas.web.support.CookieRetrievingCookieGenerator"p:cookieSecure="true" p:cookieMaxAge="-1" p:cookieName="CASTGC"p:cookiePath="/cas" />
<beanid="ticketGrantingTicketCookieGenerator" class="org.jasig.cas.web.support.CookieRetrievingCookieGenerator"
p:cookieSecure="true"
p:cookieMaxAge="-1"
p:cookieName="CASTGC"
p:cookiePath="/cas"/>
将bean中的p:cookieSecure="true"修改为p:cookieSecure="false"
3、相关类和配置文件修改:
此处修改的前提是自定义登录基类和登录验证辅助类。目前假设自定义的登录基类名为IngtaUserCredentials.java和IngtaUserCredentialsToPrincipalResolver.java,登录验证辅助类为IngtaUserAuthenticationHandler.java.
需要修改配置文件如下:
deployerConfigContext.xml:查找到<property name=”authenticationHandlers”>替换其中的<beanclass="org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler"/>/>内容修改为如下: <beanclass="包名. IngtaUserAuthenticationHandler"> <property name="sql" value="普通登录验证sql语句" /> <property name="dataSource"ref="casDataSource" /> </bean>
在此文件中添加
<beanid="casDataSource"class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<propertyname="driverClassName"><value>com.mysql.jdbc.Driver</value></property>
<property name="url"><value>jdbc:mysql://localhost:3306/cas?useUnicode=true&characterEncoding=utf-8</value></property>
<propertyname="username"><value>xxxx</value></property>
<propertyname="password"><value>xxx</value></property> </bean>数据源。
查找到<property name=”credentialsToPrincipalResplvers”>其中的<beanclass="org.jasig.cas.authentication.principal.UsernamePasswordCredentialsToPrincipalResolver">替换为<bean class=”包名.IngtaUserCredentialsToPrincipalResolver”>
login-webflow.xml修改如下:
找到<var name=”credentials”然后修改其后的class=”包名.IngtaUserCredentials”
IngtaUserCredentials.java 此文件为用户登录验证用户的基础类,此类需要实现Credentials接口,并且需要重写equals和hashCode方法。
IngtaUserCredentialsToPrincipalResolver.java此文件需要继承AbstractPersonDirectoryCredentialsToPrincipalResolver重写其中的@Override
public boolean supports(Credentialscredentials) {
return credentials !=null
&& IngtaUserCredentials.class.isAssignableFrom(credentials
.getClass());
}
@Override
protected StringextractPrincipalId(Credentials credentials) {
final IngtaUserCredentialsingtaCredentials = (IngtaUserCredentials) credentials;
returningtaCredentials.getUsername();
}
AbstractUsernamePasswordAuthenticationHandler.java此文件为抽象类,在cas源代码中有demo,需要重写此类使其接收对象为IngtaUserCredentials.class,其他和源代码中的一样。
AbstractJdbcIngtaUserAuthenticationHandler.java需要模仿AbstractJdbcUsernamePasswordAuthenticationHandler.java来进行改写,使其继承重写的AbstractUsernamePasswordAuthenticationHandler抽象类。
IngtaUserAuthenticationHandler.java文件需要继承AbstractJdbcIngtaUserAuthenticationHandler抽象类,在其中的
protected booleanauthenticateUsernamePasswordInternal(final IngtaUserCredentials credentials) throws AuthenticationException { 方法中添加自定义的登录验证方法。如果返回true则表示登录成功,返回false登录不成功。
四、数据配置在配置的数据库中新增一张表为 sso_user表,此表主要为存储过个系统的用户信息,此处为了方便,采用的是数据库的触发器,每个独立子系统的用户表变更后按照sso_user表格式,自动写到此表中。
五、自定义登录页面
登录页面可以自己定义,为了方便,此处本人指是在cas原指定的页面中进行修改。自定义页面中如果需要添加条件,需要到login-webflow.xml中找到<view-state id="viewLoginForm" view="casLoginView" model="credentials">
<binder>
在此处添加页面需要带到后端的name。
六、提交客户端更多信息
为了能够往客户端回传更多信息,可以到deployConfigContext.xml的<bean class="org.jasig.services.persondir.support.jdbc.SingleRowJdbcPersonAttributeDao"
id="ingtaRepository">
<constructor-arg index="0" ref="casDataSource"/>
<constructor-arg index="1" value="select u_code as ucode,u_type as utype from sso_user where {0}"/>
<property name="queryAttributeMapping">
<map>
<entry key="username" value="u_code"/>
</map>
</property>
<property name="resultAttributeMapping">
<map>
<entry key="ucode" value="usercode"/>
<entry key="utype" value="usertype"/>
</map>
</property>
</bean>
中进行配置需要返回的信息。同时,需要修改WEB-INF/view/jsp/protocol/2.0/casServiceValidationSuccess.jsp中的<cas:authenticationSuccess>
<cas:user>${fn:escapeXml(assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.id)}</cas:user>
<c:if test="${fn:length(assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.attributes)> 0}">
<cas:attributes>
<c:forEach var="attr" items="${assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.attributes}">
<cas:${fn:escapeXml(attr.key)}>${fn:escapeXml(attr.value)}</cas:${fn:escapeXml(attr.key)}>
</c:forEach>
</cas:attributes>
</c:if>
七、客户端调用
客户端首先放入cas-client-core-3.2.1.jar客户端中。然后在客户端的web.xml的后面几行(此处很重要),不然会导致原客户端系统的一些过滤出现问题,特别是字符集问题,添加以下代码:
<filter>
<filter-name>CAS Authentication Filter</filter-name>
<filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
<init-param>
<!-- CAS服务端登录地址 -->
<param-name>casServerLoginUrl</param-name>
<param-value>http://localhost:8080/cas/</param-value>
</init-param>
<init-param>
<!-- 当前网站域名 -->
<param-name>serverName</param-name>
<param-value>http://localhost:8080</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CAS Authentication Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 该过滤器负责对Ticket的校验工作,必须启用它 -->
<filter>
<filter-name>CAS Validation Filter</filter-name>
<filter-class>
org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter</filter-class>
<init-param>
<param-name>casServerUrlPrefix</param-name>
<param-value>http://localhost:8080/cas</param-value>
</init-param>
<init-param>
<param-name>serverName</param-name>
<param-value>http://localhost:8080</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CAS Validation Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 该过滤器负责实现HttpServletRequest请求的包裹,比如允许开发者通过HttpServletRequest的getRemoteUser()方法获得SSO登录用户的登录名,可选配置。 -->
<filter>
<filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
<filter-class>
org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CASHttpServletRequest Wrapper Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 该过滤器使得开发者可以通过org.jasig.cas.client.util.AssertionHolder来获取用户的登录名。比如AssertionHolder.getAssertion().getPrincipal().getName()。 -->
<filter>
<filter-name>CAS Assertion Thread Local Filter</filter-name>
<filter-class>org.jasig.cas.client.util.AssertionThreadLocalFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CAS Assertion Thread Local Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
其中的CAS服务端地址和本服务器地址根据需要自行修改。本服务的地址只要到ip:端口。如果有独立域名可以配置域名。
获取登录用户信息:在客户端系统的拦截器中使用
Map<String, Object> attributes = principal.getAttributes();
Object usertype=attributes.get("usertype");
Object usercode=attributes.get("usercode");
进行类似获取。
记录有点乱。