1.什么是CAS?
CAS ( Central Authentication Service ) 是 Yale 大学发起的一个企业级的、开源的项目,旨在为 Web 应用系统提供一种可靠的单点登录解决方法(属于 Web SSO )。
CAS 开始于 2001 年, 并在 2004 年 12 月正式成为 JA-SIG 的一个项目。
2.术语解释
2.1 单点登陆
对于两个相互独立的系统A、B,如果已经登陆了系统A,在访问系统B时不需要再次登陆,这就是单点登陆。
比如我们登陆了QQ,打开QQ空间或QQ邮箱时就不要再输入用户名和密码再次登陆了,这就是单点登陆。
2.2 Service Ticket
Service ticket(ST) :服务票据,服务的惟一标识码 , 由 CAS Server 发出(Http 传送),通过客户端浏览器到达业务服务器端;一个特定的服务只能有一个惟一的 ST.
ST是由CAS服务器生成,并传递给客户端,客户端再使用这个ST向CAS服务器验证身份,验证通过后客户端就通过CAS服务器的身份验证,也就可以创建session了。
ST会保存在CAS服务器中的ConcurrentHashMap,并且可以设置有效期,默认有效期是10秒,可以在配置文件spring-configuration/ticketExpirationPolicies.xml中修改。
<bean id="serviceTicketExpirationPolicy" class="org.jasig.cas.ticket.support.MultiTimeUseOrTimeoutExpirationPolicy"
c:numberOfUses="1" c:timeToKill="${st.timeToKillInSeconds:10}" c:timeUnit-ref="SECONDS"/>
2.3 Ticket Granting ticket
Ticket Granting ticket(TGT) :票据授权票据,由 KDC 的 AS 发放。即获取这样一张票据后,以后申请各种其他服务票据 (ST) 便不必再向 KDC 提交身份认证信息 (Credentials)
TGT由CAS服务器生成,保存在CAS服务器中的ConcurrentHashMap,并且存放到客户端的浏览器的cookie的参数CASTGC中,可以在配置文件spring-configuration/ticketGrantingTicketCookieGenerator.xml中修改。
<bean id="ticketGrantingTicketCookieGenerator" class="org.jasig.cas.web.support.CookieRetrievingCookieGenerator"TGT也可以设置超时时间,默认有效期是8个小时,可以在文件spring-configuration/ticketExpirationPolicies.xml中修改。
p:cookieSecure="false"
p:cookieMaxAge="-1"
p:cookieName="CASTGC"
p:cookiePath="/cas"
p:rememberMeMaxAge="43200" />
<bean id="grantingTicketExpirationPolicy" class="org.jasig.cas.ticket.support.TicketGrantingTicketExpirationPolicy" p:maxTimeToLiveInSeconds="${tgt.maxTimeToLiveInSeconds:28800}" p:timeToKillInSeconds="${tgt.timeToKillInSeconds:7200}"/>
CAS的单点登陆就是使用这个TGT来实现。
3.系统部署情况
本人使用的CAS源码包是4.0.0版本的,可以到网上下载CAS的源码包cas-server-4.0.0-release.zip
3.1 首先配置三台tomcat,它们的安装信息如下:
(1)tomcat1
端口号:8080
部署的WAR包:cas-server-webapp-4.0.0.war
可以将这个WAR包改成名cas.war
(2)tomat2
端口号:8081
部署的WAR包:buyer_center.war
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"
version="2.5">
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath*:applicationContext-servlet.xml
</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<filter>
<filter-name>encoding-filter</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>encoding-filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>annomvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:applicationContext-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>annomvc</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
<filter>
<filter-name>CAS Authentication Filter</filter-name>
<filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
<init-param>
<param-name>casServerLoginUrl</param-name>
<param-value>http://login.mycompany.com:8080/cas/login</param-value>
</init-param>
<init-param>
<param-name>serverName</param-name>
<param-value>http://buyer.mycompany.com:8081</param-value>
</init-param>
</filter>
<filter>
<filter-name>CAS Validation Filter</filter-name>
<filter-class>org.jasig.cas.client.validation.Cas10TicketValidationFilter</filter-class>
<init-param>
<param-name>casServerUrlPrefix</param-name>
<param-value>http://login.mycompany.com:8080/cas</param-value>
</init-param>
<init-param>
<param-name>serverName</param-name>
<param-value>http://buyer.mycompany.com:8081</param-value>
</init-param>
<init-param>
<param-name>redirectAfterValidation</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter>
<filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
<filter-class>org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class>
</filter>
<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 Authentication Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>CAS Validation Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>CAS Assertion Thread Local Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
applicationContext-servlet.xml中的内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"
default-lazy-init="true">
<mvc:annotation-driven/>
<context:component-scan base-package="com.controller"/>
<mvc:default-servlet-handler/>
</beans>
(3)tomcat3
端口号:8083
部署的WAR包:seller_center.war
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"
version="2.5">
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath*:applicationContext-servlet.xml
</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<filter>
<filter-name>encoding-filter</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>encoding-filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>annomvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:applicationContext-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>annomvc</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
<filter>
<filter-name>CAS Authentication Filter</filter-name>
<filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
<init-param>
<param-name>casServerLoginUrl</param-name>
<param-value>http://login.mycompany.com:8080/cas/login</param-value>
</init-param>
<init-param>
<param-name>serverName</param-name>
<param-value>http://seller.mycompany.com:8083</param-value>
</init-param>
</filter>
<filter>
<filter-name>CAS Validation Filter</filter-name>
<filter-class>org.jasig.cas.client.validation.Cas10TicketValidationFilter</filter-class>
<init-param>
<param-name>casServerUrlPrefix</param-name>
<param-value>http://login.mycompany.com:8080/cas</param-value>
</init-param>
<init-param>
<param-name>serverName</param-name>
<param-value>http://seller.mycompany.com:8083</param-value>
</init-param>
<init-param>
<param-name>redirectAfterValidation</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter>
<filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
<filter-class>org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class>
</filter>
<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 Authentication Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>CAS Validation Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>CAS Assertion Thread Local Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
applicationContext-servlet.xml中的内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"
default-lazy-init="true">
<mvc:annotation-driven/>
<context:component-scan base-package="com.controller"/>
<mvc:default-servlet-handler/>
</beans>
3.2 配置HOST文件中的域名
127.0.0.1 login.mycompany.com
127.0.0.1 seller.mycompany.com
127.0.0.1 buyer.mycompany.com
4.CAS实现单点登陆的原理
4.1 访问buyer
buyer.mycompany.com:8081/buyer_center/loginController/login.do4.2 跳转到CAS登陆页面,并且将访问的URL放到CAS跳转参数service中
http://login.mycompany.com:8080/cas/login?service=http%3A%2F%2Fbuyer.mycompany.com%3A8081%2Fbuyer_center%2FloginController%2Flogin.do
4.3 登陆
输入用户名和密码后就登陆到CAS中,登陆成功后CAS会创建一个Ticket Granting ticket(TGT) ,把它保存在DefaultTicketRegistry中的
Map<String, Ticket> cache = new ConcurrentHashMap<String, Ticket>();并且CAS服务器也会将这个TGT保存到客户端的浏览器的cookie中.
登陆成功后输入:http://login.mycompany.com:8080/cas/login,可以看到TGT。
4.4 CAS登陆成功后跳转service的地址,并且附带一个Service Ticket
buyer.mycompany.com:8081/buyer_center/loginController/login.do?ticket=ST-12-Mfq4OVkBAlddDyx1ucRq-cas01.example.org
客户端buyer_center会去拿到这个ST,并且发送HTTP请求到CAS中去验证,验证通过后客户端就会创建session,并且跳转到
buyer.mycompany.com:8081/buyer_center/loginController/login.do
这样就完成了登陆。
4.5 单点登陆
前面4步已经完成了系统buyer_center的登陆,下面将介绍单点登陆到seller_center的过程。
(1)访问另一系统seller_center
http://seller.mycompany.com:8083/seller_center/loginController/login.do
客户端seller_center的拦截器AuthenticationFilter拦截到当前访问没有session,拦截器Cas10TicketValidationFilter拦截到当前URL没有ST,所以转发到CAS登陆URL。
http://login.mycompany:8080/cas/login?service=http%3A%2F%2Fseller.mycompany.com%3A8083%2Fseller_center%2FloginController%2Flogin.do
(2)CAS服务器会去读取cookie TGT
CAS 服务器会去读取域名login.mycompany.com下的cookie变量CASTGC(里面存放着TGT的值).
CAS服务器会去验证这个TGT是否合法,如果合法,则会创建一个ST并且返回
http://seller.mycompany.com:8083/seller_center/loginController/login.do?ticket=ST-12-Mfq4OVkBAlddDyx1ucRq-cas01.example.org
(3)验证ST合法性
客户端seller_center拿到这个ST后,就会向CAS服务器发送一个HTTP请求,验证ST的合法性,如果合法,则创建一个session.
这样seller_center不需要登陆就完成了单点登陆。
5.CAS实现单点登出的原理
后续...
6.总结
6.1 CAS是比较成熟的单点登陆解决方案
6.2 CAS性能很差,单台机的并发量只有十几个
6.2 CAS在实际项目的应用中需要做大量的定制开发,需要修改很多源码,会占用开发人员大量的时间和精力。