一、概述
单点登录(Single Sign On , 简称 SSO )是目前比较流行的服务于企业业务整合的解决方案之一, SSO 使得在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。CAS(Central Authentication Service)是一款不错的针对 Web 应用的单点登录框架,本文介绍了 CAS 的原理、协议、在 Tomcat 中的配置和使用,对于采用 CAS 实现轻量级单点登录解决方案的入门读者具有一定指导作用。
二、CAS介绍
CAS 是 Yale 大学发起的一个开源项目,旨在为 Web 应用系统提供一种可靠的单点登录方法,CAS 在 2004 年 12 月正式成为 JA-SIG 的一个项目(http://www.jasig.org)。CAS 具有以下特点:
1)开源的企业级单点登录解决方案
2)CAS Server 为需要独立部署的 Web 应用
3)CAS Client 支持非常多的客户端(指Web 应用),包括Java,.Net,PHP,Perl,Ruby 等
三、CAS原理及协议
从结构上看,CAS 包含两个部分: CAS Server 和 CAS Client。CAS Server 需要独立部署,主要负责对用户的认证工作;CAS Client 负责处理对客户端受保护资源的访问请求,需要登录时,重定向到 CAS Server。AS 最基本的协议过程:
CAS Client 与受保护的客户端应用部署在一起,以 Filter 方式保护受保护的资源。对于访问受保护资源的每个 Web 请求,CAS Client 会分析该请求的 Http 请求中是否包含 Service Ticket,如果没有,则说明当前用户尚未登录,于是将请求重定向到指定好的 CAS Server 登录地址,并传递 Service (也就是要访问的目的资源地址),以便登录成功过后转回该地址。用户在第 3 步中输入认证信息,如果登录成功,CAS Server 随机产生一个相当长度、唯一、不可伪造的 Service Ticket,并缓存以待将来验证,之后系统自动重定向到 Service 所在地址,并为客户端浏览器设置一个 Ticket Granted Cookie(TGC),CAS Client 在拿到 Service 和新产生的 Ticket 过后,在第 5,6 步中与 CAS Server 进行身份合适,以确保 Service Ticket 的合法性。
在该协议中,所有与 CAS 的交互均采用 SSL 协议,确保,ST 和 TGC 的安全性。协议工作过程中会有 2 次重定向的过程,但是 CAS Client 与 CAS Server 之间进行 Ticket 验证的过程对于用户是透明的。
另外,CAS 协议中还提供了 Proxy (代理)模式,以适应更加高级、复杂的应用场景,具体介绍可以参考 CAS 官方网站上的相关文档。
四、CAS SERVER配置
1.准备工作
安装配置JDK、安装Tomcat7,此处不做详解。
到CAS官网下载CAS Server和Client,地址如下:
http://downloads.jasig.org/cas/cas-server-4.0.0-release.zip
http://downloads.jasig.org/cas-clients/cas-client-3.2.1-release.zip
2.部署
1.将下载的cas-server-4.0.0-release.zip解开,把cas-server-4.0.0/modules/cas-server-webapp-4.0.0.war拷贝到 tomcat的webapps目录,并更名为cas.war。
2.修改cas\WEB-INF\spring-configuration\ticketGrantingTicketCookieGenerator.xml文件,将属性p:cookieSecure="true" 变成 p:cookieSecure="false"(这个设置主要是让CAS不走SSL协议,详见:让CAS不用SSL也可实现跨域)
3.启动tomcat,然后访问:http://localhost:8888/cas,如果能出现正常的CAS登录页面,则说明CAS Server 已经部署成功。如下图:
虽然 CAS Server 已经部署成功,但这只是一个缺省的实现,在实际使用的时候,还需要根据实际概况做扩展和定制,最主要的是扩展认证 (Authentication) 接口和 CAS Server 的界面。
五、CAS Client配置
将下载的cas-client-3.2.1-release.zip解开,将cas-client-3.2.1/modules/
cas-client-core-3.2.1.jar,放入你的web项目lib目录中,修改web.xml,添加如下web.xml中单点登录块配置信息:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="WebApp_ID" version="2.5">
<display-name>CasClientOne</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<!-- ======================== 单点登录结束 ======================== -->
<!-- 用于单点退出,该过滤器用于实现单点登出功能,可选配置 -->
<listener>
<listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
</listener>
<!-- 该过滤器用于实现单点登出功能,可选配置。 -->
<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>
<!-- 该过滤器负责用户的认证工作,必须启用它 -->
<filter>
<filter-name>CASFilter</filter-name>
<filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
<init-param>
<param-name>casServerLoginUrl</param-name>
<param-value>http://localhost:8888/cas/login</param-value><!-- cas 服务器登录 地址 http://IP:PORT/CasWebProName/login -->
</init-param>
<init-param>
<!-- 这里的server是服务端的IP -->
<param-name>serverName</param-name>
<param-value>http://localhost:8080</param-value><!-- 客户端服务器地址 http://IP:PORT -->
</init-param>
</filter>
<filter-mapping>
<filter-name>CASFilter</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:8888/cas</param-value><!-- cas 服务器地址 http://IP:PORT/CasWebProName -->
</init-param>
<init-param>
<param-name>serverName</param-name>
<param-value>http://localhost:8080</param-value><!-- 客户端服务器地址 http://IP:PORT -->
</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>CAS HttpServletRequest 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>
<!-- ======================== 单点登录结束 ======================== -->
</web-app>
配置完毕后,启动tomcat,然后访问:http://IP:PORT/WebProName/index.jsp,如果系统跳转到CAS登录页面,输入用户名/密码(正确的)后,会跳转到http://IP:PORT/WebProName/index.jsp,则说明CAS Client已经部署成功。
注:到此CAS框架已搭建完毕,此种方法未采用SSL协议(需要修改Cas),如果想采用SSL协议方式,则在上述配置中需要以下调整:
- 修改Cas Server所处Web容器(本文为Tomcat),使其支持SSL协议
- Cas Server部署时,不需要修改cas\WEB-INF\spring-configuration\ticketGrantingTicketCookieGenerator.xm
- Cas Client web.xml中配置的Cas Server相关地址需要将http变更成https
六、JDBC认证方式
1.相关JAR包准备
cas-server-4.0.0-release.zip\cas-server-4.0.0\modules\cas-server-support-jdbc-4.0.0.jar及相应的数据库驱动包(这里是MySQL驱动mysql-connector-java-5.1.7-bin.jar)复制到cas/WEB-INF/lib目录下。
2.修改deployerConfigContext.xml1.
<!-- 注释掉原本固定登录用户 -->
<!-- <bean id="primaryAuthenticationHandler" -->
<!-- class="org.jasig.cas.authentication.AcceptUsersAuthenticationHandler"> -->
<!-- <property name="users"> -->
<!-- <map> -->
<!-- <entry key="casuser" value="Mellon"/> -->
<!-- </map> -->
<!-- </property> -->
<!-- </bean> -->
<!-- 变更为JDBC验证方式 -->
<bean id="primaryAuthenticationHandler" class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler">
<property name="dataSource" ref="dataSource"></property>
<property name="sql" value="select password from user where user_name=?"></property>
<property name="passwordEncoder" ref="MD5PasswordEncoder"></property>
</bean>
<!-- 数据源配置 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName"><value>com.mysql.jdbc.Driver</value></property>
<property name="url"><value>jdbc:mysql://localhost:3309/ossm?characterEncoding=utf8</value></property>
<property name="username"><value>root</value></property>
<property name="password"><value>root</value></property>
</bean>
<!-- 添加MD5密码加密功能 -->
<bean id="MD5PasswordEncoder" class="org.jasig.cas.authentication.handler.DefaultPasswordEncoder">
<constructor-arg index="0">
<value>MD5</value>
</constructor-arg>
</bean>
七、客户端获取用户登录信息
CAS登录成功默认返回的只有用户名,
JAVA客户端获取:
AttributePrincipal principal = (AttributePrincipal) request.getUserPrincipal();
String username = principal.getName();
而在实际应用中,客户端需要知道更多的用户信息,比如用户的性别,年龄,爱好,地址,用户的分组,角色信息等等,下面介绍如何给客户端返回更多的用户资料和信息:
1.修改cas/WEB-INF/deployerConfigContext.xml,注释掉原有的attributeRepository及attributeRepository的引用信息,添加如下信息:
<bean id="attributeRepository" class="org.jasig.services.persondir.support.jdbc.SingleRowJdbcPersonAttributeDao">
<constructor-arg index="0" ref="dataSource"/>
<constructor-arg index="1" value="select * from user where {0}"/>
<!-- <constructor-arg index="1" value="select * from user where {0} {1}"/> -->
<!-- 组装sql用的查询条件属性 -->
<property name="queryAttributeMapping">
<map>
<!-- 这里的key需写username,value对应数据库用户名字段 -->
<entry key="username" value="user_name"/>
<!-- <entry key="id" value="id"/> -->
</map>
</property>
<!-- 如果要组装多个查询条件,需要加上下面这个,默认为AND -->
<property name="queryType">
<value>OR</value>
</property>
<!-- 要获取的属性在这里配置 -->
<property name="resultAttributeMapping">
<map>
<!--key为对应的数据库字段名称,value为提供给客户端获取的属性名字,系统会自动填充值-->
<entry key="id" value="id"/>
<entry key="user_name" value="username"/>
<entry key="phone" value="phone"/>
<entry key="email" value="email"/>
</map>
</property>
</bean>
2.修改cas/WEB-INF/view/jsp/protocol/2.0/casServiceValidationSuccess.jsp,添加下方标红部分:1.
<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
<cas:authenticationSuccess>
<cas:user>${fn:escapeXml(assertion.primaryAuthentication.principal.id)}</cas:user>
<%-- 返回更多用户信息配置 By Goma --%>
<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>
<c:if test="${not empty pgtIou}">
<cas:proxyGrantingTicket>${pgtIou}</cas:proxyGrantingTicket>
</c:if>
<c:if test="${fn:length(assertion.chainedAuthentications) > 1}">
<cas:proxies>
<c:forEach var="proxy" items="${assertion.chainedAuthentications}" varStatus="loopStatus" begin="0" end="${fn:length(assertion.chainedAuthentications)-2}" step="1">
<cas:proxy>${fn:escapeXml(proxy.principal.id)}</cas:proxy>
</c:forEach>
</cas:proxies>
</c:if>
</cas:authenticationSuccess>
</cas:serviceResponse>
3.客户端取值3.
AttributePrincipal principal = (AttributePrincipal) request.getUserPrincipal();
String username = principal.getName();
Map<String,Object> attributes = principal.getAttributes();
String email = attributes .get("email") + "";
八、界面定制
CAS 提供了一套默认的页面,在目录cas/WEB-INF/view/jsp/default下。在部署 CAS 之前,我们可能需要定制一套新的CAS Server页面,添加一些个性化的内容。最简单的方法就是拷贝一份 default文件到“ cas/WEB-INF/view/jsp ”目录下,比如命名为newUI,接下来是实现和修改必要的页面,有 4 个页面是必须的:
casConfirmView.jsp: 当用户选择了“warn”时会看到的确认界面
casGenericSuccess.jsp: 在用户成功通过认证而没有目的Service时会看到的界面
casLoginView.jsp: 当需要用户提供认证信息时会出现的界面
casLogoutView.jsp: 当用户结束 CAS 单点登录系统会话时出现的界面
CAS 的页面采用 Spring框架编写,对于不熟悉 Spring 的使用者,在修改之前需要熟悉该框架。
页面定制完过后,还需要做一些配置从而让 CAS 找到新的页面,拷贝cas/WEB-INF/classes/default_views.properties,重命名为as/WEB-INF/classes/ newUI_views.properties,并修改其中所有的值到相应新页面。最后是更新cas/WEB-INF/cas-servlet.xml文件中的 viewResolver,将protocol_views更改为newUI_views。
九、其他功能扩展
增加验证码、密码有效期、限制用户登录之类的功能这里不做描述,如果需要请自行查找。