CAS 介绍

CAS 是 Yale 大学发起的一个开源项目,旨在为 Web 应用系统提供一种可靠的单点登录方法,CAS 在 2004 年 12 月正式成为 JA-SIG 的一个项目。CAS 具有以下特点:

  • 开源的企业级单点登录解决方案。
  • CAS Server 为需要独立部署的 Web 应用。
  • CAS Client 支持非常多的客户端(这里指单点登录系统中的各个 Web 应用),包括 Java, .Net, PHP, Perl, Apache, uPortal, Ruby 等。

CAS 原理和协议

从结构上看,CAS 包含两个部分: CAS Server 和 CAS Client。CAS Server 需要独立部署,主要负责对用户的认证工作;CAS Client 负责处理对客户端受保护资源的访问请求,需要登录时,重定向到 CAS Server。下图 是 CAS 最基本的协议过程:

在图中第3步用户认证成功后,cas server会生成Ticket Granting Ticket(票据授权票据,简称TGT),同时将TGT值以CASTGC为名保存到浏览器的cookie中,之后生成Service Ticket(服务票据,简称ST)并缓存,在第4步时将ST通过浏览器重定向的URL传给cas client。

当cas client验证ST时,是在后台请求cas server验证ST,而cas server在已缓存的ST中查找是否存在cas client传来的ST,若存在则返回验证成功同时将该ST删除(这就保证了用同一ST不能反复进入client应用,同时这也是为什么不直接将TGT返回给cas client的原因)。

那么名为CASTGC的cookie起什么作用呢?

当客户访问另一个cas client时,同样会被重定向到cas server,而此时我们并不希望再次让用户输入用户密码登陆,名为CASTGC的cookie这时就体现出作用来了,cas server发现存在名为CASTGC的cookie就将其值在已保存的TGT中查找,若存在,则说明已存在合法的TGT,cas server就根据该TGT生成新的ST,接下来的流程就和以前一样了。

CAS 协议中还提供了 Proxy (代理)模式,以适应更加高级、复杂的应用场景,具体介绍可以参考 CAS 官方网站上的相关文档。

在多点环境下使用CAS

虽然cas是作为开源单点登录解决方案的一个不错选择,但是官方提供的缺省实现代码却不并支持cas server多点部署以及每个cas client多点部署的情况。这在现今越来越强调服务稳定性的潮流下,多少显得有些不合时宜。那么是不是服务是多点部署就不能使用cas了呢?答案是否定的。

多点部署cas server

对cas server来说,默认实现不能支持多点部署的原因在于TGT保存时使用的ticket register类将TGT保存在了Java类的变量中。相关配置如下:

WEB-INF\spring-configuration\ticketRegistry.xml:

  1. <bean id="ticketRegistry" class="org.jasig.cas.ticket.registry.DefaultTicketRegistry" />

如果要支持多点部署,我们可以通过引入memcached的方式,在多点环境下仍然能够正常使用cas。我们可以新创建一个类(如MemcachedTicketRegistry)实现AbstractTicketRegistry接口,修改相关配置如下:

WEB-INF\spring-configuration\ticketRegistry.xml:(在此省略了memcachedClient的配置)

  1. <bean id="ticketRegistry" class="com.xxx.cas.server.MemcachedTicketRegistry">
  2. <property name="client" ref="memcachedClient" />
  3. </bean>

这样cas server已经可以多点部署了,然而此时我们会发现单点登出功能不正常了。通过debug和查看代码,发现TGT中保存的service集合为空,这是单点登出不正常的直接原因,因为cas server会遍历TGT中保存的service集合,依次向对应的cas client发出退出请求。然而为什么TGT中保存的service集合会为空呢?这是因为TGT从第一次被保存到memcached后就再也没有被保存到memcached,这样从memcached中取得的TGT自然还是最初的TGT,当然其中的service会为空了,而cas默认实现中TGT是始终保持在内存中的自然不会有问题。既然找到了问题的原因就简单了,我们只要每当TGT增加service后,再次将TGT保存到memcached就能解决这个问题。

WEB-INF\spring-configuration\applicationContext.xml修改如下:

  1. <bean id="centralAuthenticationService" class="org.jasig.cas.CentralAuthenticationServiceImpl" ... />
  2. 替换为
  3. <bean id="centralAuthenticationService" class="com.xxx.cas.server.CentralAuthenticationServiceImpl" ... />

com.xxx.cas.server.CentralAuthenticationServiceImpl类相比org.jasig.cas.CentralAuthenticationServiceImpl类有如下不同:

  1. this.serviceTicketRegistry.addTicket(serviceTicket);
  2. //com.xxx.cas.server.CentralAuthenticationServiceImp类增加了下面一行:
  3. this.serviceTicketRegistry.addTicket(ticketGrantingTicket);

多点部署cas client

对cas client来说,默认实现不能支持多点部署的原因在于cas client使用了session来保存凭证,要知道多点部署的应用其session是各自独立的,即使通过配置实现了session的同步,性能也会很差。那么如何解决这个问题呢?答案还是memcached。

我们可以用过滤器filter将自定义的request传递给后面的调用,filter内容如下:

  1. SnaHttpServletRequest request = new SnaHttpServletRequest((HttpServletRequest) req,(HttpServletResponse) res, client);
  2. WebContext instance = new WebContext(request, (HttpServletResponse) res, ctx);
  3. try {
  4. WebContext.set(instance);
  5. chain.doFilter(request, res);
  6. } finally {
  7. request.save();
  8. WebContext.set(null);
  9. }

其中SnaHttpServletRequest类继承自HttpServletRequestWrapper类,覆盖HttpSession getSession()和HttpSession getSession(boolean create)方法,使这两个方法返回实现HttpSession接口但是以memcached为核心实现的类(如MemcachedSession),而MemcachedSession类必定是以cookie为依据的,否则无法返回正确的数据。

这其中代码的具体实现,网上已经有不少,这里就不展示了。

cas client经过这样的改动后,在多点部署下可以单点登陆了,但单点登出仍有问题。原因是因为SingleSignOutFilter将ticketId和session对应关系用HashMap来保存,在多点环境下自然不能正常工作。我们可以将ticketId和cookie值的对应关系保存在memcached上,从而实现单点登出。修改办法如下:

SingleSignOutFilter类:

  1. public void init(final FilterConfig filterConfig) throws ServletException {
  2. String memcachedId = "memcachedClient";
  3. this.client = (XMemcachedClient) WebApplicationContextUtils.getWebApplicationContext(filterConfig.getServletContext()).getBean(memcachedId);
  4. handler.setSessionMappingStorage(new MemcachedBackedSessionMappingStorage(client));
    //红色部分是新增的内容
  5.  
  6. if (!isIgnoreInitConfiguration()) {
  7. handler.setArtifactParameterName(getPropertyFromInitParams(filterConfig, "artifactParameterName", "ticket"));
  8. handler.setLogoutParameterName(getPropertyFromInitParams(filterConfig, "logoutParameterName", "logoutRequest"));
  9. }
  10. handler.init();
  11. }

MemcachedBackedSessionMappingStorage类继承自SessionMappingStorage接口,代码如下:

  1. public final class MemcachedBackedSessionMappingStorage implements SessionMappingStorage {
  2. private XMemcachedClient client;
  3. private static final int TIMEOUT = 60 * 60 * 24;
  4.  
  5. private final String MANAGED_SESSIONS = "MANAGED_SESSIONS.";
  6. private final String ID_TO_SESSION_KEY_MAPPING = "ID_TO_SESSION_KEY_MAPPING.";
  7.  
  8. private final Log log = LogFactory.getLog(getClass());
  9.  
  10. public MemcachedBackedSessionMappingStorage(XMemcachedClient client) {
  11. this.client = client;
  12. }
  13.  
  14. public synchronized void addSessionById(String mappingId, HttpSession session) {
  15. try {
  16. client.set(ID_TO_SESSION_KEY_MAPPING + session.getId(), TIMEOUT, mappingId);
  17. client.set(MANAGED_SESSIONS + mappingId, TIMEOUT, session.getId());
  18. } catch (Exception e) {
  19. throw new RuntimeException(e);
  20. }
  21.  
  22. }
  23.  
  24. public synchronized void removeBySessionById(String sessionId) {
  25. if (log.isDebugEnabled()) {
  26. log.debug("Attempting to remove Session=[" + sessionId + "]");
  27. }
  28.  
  29. try {
  30. final String key = client.get(ID_TO_SESSION_KEY_MAPPING + sessionId);
  31.  
  32. if (log.isDebugEnabled()) {
  33. if (key != null) {
  34. log.debug("Found mapping for session. Session Removed.");
  35. } else {
  36. log.debug("No mapping for session found. Ignoring.");
  37. }
  38. }
  39. client.delete(MANAGED_SESSIONS + key);
  40. client.delete(ID_TO_SESSION_KEY_MAPPING + sessionId);
  41. } catch (Exception e) {
  42. throw new RuntimeException(e);
  43. }
  44. }
  45.  
  46. public synchronized HttpSession removeSessionByMappingId(String mappingId) {
  47. HttpSession session = null;
  48. try {
  49. String sessionId = client.get(MANAGED_SESSIONS + mappingId);
  50. session = new MemcachedSession(client, sessionId, false);
  51. } catch (Exception e) {
  52. throw new RuntimeException(e);
  53. }
  54.  
  55. if (session != null) {
  56. removeBySessionById(session.getId());
  57. }
  58.  
  59. return session;
  60. }
  61. }

其它修改

在CAS的默认实现中,所有与 CAS 的交互均采用 SSL 协议,确保ST 和 TGC(Ticket Granted Cookie,对应TGT的名为CASTGC的cookie) 的安全性。这虽然极大保证了安全性,但在某些情况下,并不想使用SSL协议,那么可以进行如下修改:

WEB-INF\spring-configuration\ticketGrantingTicketCookieGenerator.xml:

  1. <bean id="ticketGrantingTicketCookieGenerator" class="org.jasig.cas.web.support.CookieRetrievingCookieGenerator"
  2. p:cookieSecure="true"
  3. p:cookieMaxAge="-1"
  4. p:cookieName="CASTGC"
  5. p:cookiePath="/cas" />
  6. 修改为:
  7. <bean id="ticketGrantingTicketCookieGenerator" class="org.jasig.cas.web.support.CookieRetrievingCookieGenerator"
  8. p:cookieSecure="false"
  9. p:cookieMaxAge="-1"
  10. p:cookieName="CASTGC"
  11. p:cookiePath="/cas" />

WEB-INF\spring-configuration\warnCookieGenerator.xml:

  1. <bean id="warnCookieGenerator" class="org.jasig.cas.web.support.CookieRetrievingCookieGenerator"
  2. p:cookieSecure="true"
  3. p:cookieMaxAge="-1"
  4. p:cookieName="CASPRIVACY"
  5. p:cookiePath="/cas" />
  6. 修改为:
  7. <bean id="warnCookieGenerator" class="org.jasig.cas.web.support.CookieRetrievingCookieGenerator"
  8. p:cookieSecure="false"
  9. p:cookieMaxAge="-1"
  10. p:cookieName="CASPRIVACY"
  11. p:cookiePath="/cas" />

WEB-INF\deployerConfigContext.xml:

  1. <bean class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler"
  2. p:httpClient-ref="httpClient" />
  3. 修改为:
  4. <bean class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler"
  5. p:httpClient-ref="httpClient" p:requireSecure="false"/>

进行上面的修改后cas就能支持普通http协议的单点登录了。

在多点环境下使用cas实现单点登陆及登出的更多相关文章

  1. IdentityServer4之SSO(基于OAuth2.0、OIDC)单点登录、登出

    IdentityServer4之SSO(基于OAuth2.0.OIDC)单点登录.登出 准备  五个Web站点: 1.localhost:5000 :                  认证服务器.2 ...

  2. CAS做单点登陆(SSO)——集成BIEE 11g

    BIEE 11G和CAS集成零代码编写,只需配置. 更改BIEE analytics应用的web.xml 将analytics.war解包(使用7-zip或者Win-rar就可以),然后修改WEB-I ...

  3. [置顶] SSO单点登录系列6:cas单点登录防止登出退出后刷新后退ticket失效报500错

    这个问题之前就发现过,最近有几个哥们一直在问我这个怎么搞,我手上在做另一个项目,cas就暂时搁浅了几周.现在我们来一起改一下你的应用(client2/3)的web.xml来解决这个2b问题,首先看下错 ...

  4. 在tomcat集群下利用redis实现单点登陆

    场景:比如说我们要实现一个集群环境,无非是把多个项目部署到多个tomcat下,然后按照一定的算法,轮询什么的随机访问多个tomcat服务器,但是问题也会有许多,比如说,我们最开始是把登陆人的信息存放到 ...

  5. SSO单点登录系列6:cas单点登录防止登出退出后刷新后退ticket失效报500错

    这个问题之前就发现过,最近有几个哥们一直在问我这个怎么搞,我手上在做另一个项目,cas就暂时搁浅了几周.现在我们来一起改一下你的应用(client2/3)的web.xml来解决这个2b问题,首先看下错 ...

  6. cas sso单点登录系列6_cas单点登录防止登出退出后刷新后退ticket失效报500错

    转(http://blog.csdn.net/ae6623/article/details/9494601) 问题: 我登录了client2,又登录了client3,现在我把client2退出了,在c ...

  7. SSO单点登录一:cas单点登录防止登出退出后刷新后退ticket失效报500错,也有退出后直接重新登录报票根验证错误

    问题1: 我登录了client2,又登录了client3,现在我把client2退出了,在client3里面我F5刷新了一下,结果页面报错: 未能够识别出目标 'ST-41-2VcnVMguCDWJX ...

  8. centos7下ldap+kerberos实现单点登陆

    一. LDAP概念 http://wiki.jabbercn.org/index.php/OpenLDAP2.4%E7%AE%A1%E7%90%86%E5%91%98%E6%8C%87%E5%8D%9 ...

  9. cas单点登录防止登出退出后刷新后退ticket失效报500错

    https://www.cnblogs.com/wangyang108/p/5844447.html

随机推荐

  1. teamviewer13报错

    用自己的笔记本电脑远程桌面AGV电脑在终端运行teamviewer报错如下: Init...CheckCPU: SSE2 support: yesChecking setup...Launching ...

  2. 网页结构——head标签内

    之前写网页都很标准的格式,最近一个项目出现了页面闪动等一系列问题[项目不是前后端分离], 所以这边有后台的功劳,有部分后台是不管你页面结构的,在他们操作的时候可能会在,你的head内meta前加内联c ...

  3. mysql-essential-5.1.55-win32 安装

    1.选择无事物安装 2.my.cnf [mysqld] default-storage-engine=INNODB innodb=on 3.设置数据目录 手动创建目录 D:\data [mysqld] ...

  4. Delphi Modbus RTU CRC16校验码

    function CheckCrc16(const ABuf; ALen: Integer): Boolean;var uwTemp: WORD; i, j: BYTE; P: PByte;begin ...

  5. JAVA常见算法题(十五)

    package com.xiaowu.demo; /** * * 输入三个整数x,y,z,请把这三个数由小到大输出. * * @author WQ * */ public class Demo15 { ...

  6. numpy中结构数组

    在c语言中,我们可以使用关键字struct定义结构类型.和c语言一样,numpy也可以创建结构定义,这样可以很方便的读取二进制的C语言结构数组,将其转换为numpy数组对象,假设我们定义的结构数组如下 ...

  7. appium Parameters were incorrect

    raise exception_class(value) selenium.common.exceptions.WebDriverException: Message: Parameters were ...

  8. Swift入门(一)——基本的语法

    近期開始学习swift.把学习的过程和总结整理成一个系列.方便日后回想总结. 基本的语法 基础语法 swift中每一行结束后不须要加分号.多个语句在同一行内须要用分好隔开 //表示凝视.或者用/* - ...

  9. 设置textField的placegolder的字体大小和字体颜色

           由于项目的主题颜色为灰黑色,所以当使用textField的时候,placeholder内的字体默认是灰色,当程序执行的时候,差点儿看不到.        翻来翻去找到一种比較简单地方法, ...

  10. Scala和Java二种方式实战Spark Streaming开发

    一.Java方式开发 1.开发前准备:假定您以搭建好了Spark集群. 2.开发环境采用eclipse maven工程,需要添加Spark Streaming依赖. 3.Spark streaming ...