CAS实现SSO单点登录原理

这篇博客详细介绍了CAS实现SSO单点登录的原理,包括安全性、Cookie与Session的角色,以及登录和登出的过程。用户登录时,CAS创建Cookie(TGC)并在客户端与服务器间使用Ticket进行身份验证,确保密码安全。登出时,系统通过Cookie识别用户并通知所有已登录应用。文章还提及了客户端和服务器端的实现细节及消息流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

yale cas可以百度一下,这是学习cas后的一点总结,以备日后使用!

安全性:

用户只须在cas录入用户名和密码,之后通过ticket绑定用户,在cas客户端与cas校验是通过ticket,并不会在网上传输密码,所以可以保证安全性,密码不被窃取

原理:1个cookie+N个session

CAS创建cookie在所有应用中登录时cas使用,各应用通过在IE创建各自的session来标识应用是否已经登录。

Cookie:在cas为各应用登录时使用,实现了只须一次录入用户密码

Session:各应用会创建自己的session表示是否登录

登录

1.  CAS 登录时处理:

第一步:cas往浏览器增加cookie(TGC)

CAS向浏览器送回一个所谓的“内存cookie”。这种cookie并不是真的保存在内存中,而只是浏览器一关闭,cookie就自动过期。这个cookie称为“ticket-granting cookie”,用来表明用户已经成功地登录。

这个Cookie是一个加密的Cookie,其中保存了用户登录的信息。用于以后其它应用客户端登录。

第二步:cas同时创建一个ticket重定向到原来的cas客户端

认证成功后,CAS服务器创建一个很长的、随机生成的字符串,称为“Ticket”。随后,CAS将这个ticket和成功登录的用户,以及服务联系在一起。这个ticket是一次性使用的一种凭证,它只对登录成功的用户及其服务使用一次。使用过以后立刻失效。

2.  Cas 客户端应用的处理

第一步:收到ticket后,向cas提交验证ticket

Cas客户端收到ticket之后,应用程序需要验证ticket。这是通过将ticket 传递给一个校验URL来实现的。校验URL也是CAS服务器提供的。CAS通过校验路径获得了ticket之后,通过内部的数据库对其进行判断。如果判断是有效性,则返回一个NetID给应用程序。随后CAS将ticket作废,并且在客户端留下一个cookie。(谁来创建cookie?),

第二步:ticket验证后创建session

          以后登录此应用时,没有ticket,但IE能提供session,从session中取得CASReceipt,并验证如果有效说明已经在此应用认证过,允许访问此应用,

       到此为止,CAS会记录用户已在应用A已经登录

3.  用户登录到应用是如何处理

  用户进入应用B时,首先仍然会重定向到CAS服务器。不过此时CAS服务器不再要求用户输 入用户名和密码,而是首先自动寻找Cookie,根据Cookie中保存的信息,进行登录。然后,CAS同样给出新的ticket重定向应用B给cas验证(流程同应用A验证方式),如果验证成功则应用B创建session记录CASReceipt信息到session中,以后凭此session登录应用B。

到此为止,CAS会记录用户已在应用A和应用B进行登录,但是当用户在应用B退出cas登录时,要通知应用A进行退出,如何通知应用A呢?

      

登出

   
  CAS server接受请求后,会检测用户的TCG Cookie,把对应的session清除,同时会找到所有通过该TGC sso登录的应用服务器URL提交请求,所有的回调请求中,包含一个参数logoutRequest,内容格式如下:

< samlp:LogoutRequest  ID ="[RANDOM ID]"  Version ="2.0"  IssueInstant ="[CURRENT DATE/TIME]" >
< saml:NameID > @NOT_USED@ </ saml:NameID >
< samlp:SessionIndex > [SESSION IDENTIFIER] </ samlp:SessionIndex >
</ samlp:LogoutRequest >


所有收到请求的应用服务器application会解析这个参数,取得sessionId,根据这个Id取得session后,把session删除。
这样就实现单点登出的功能。

知道原理后,下面是结合源代码来讲述一下内部的代码怎么实现的。


客户端实现:
首先,要实现single sign out在 应用服务器application端的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 >

注:如果有配置CAS client Filter,则CAS Single Sign Out Filter 必须要放到CAS client Filter之前。

配置部分的目的是在CAS server回调所有的application进行单点登出操作的时候,需要这个filter来实现session清楚。

主要代码如下:
org.jasig.cas.client.session.SingleSignOutFilter
 1  public   void  doFilter( final  ServletRequest servletRequest,  final  ServletResponse servletResponse,  final  FilterChain      
 2 
 3  filterChain)  throws  IOException, ServletException {
 4           final  HttpServletRequest request  =  (HttpServletRequest) servletRequest;
 5 
 6           if  ( " POST " .equals(request.getMethod())) {
 7               final  String logoutRequest  =  request.getParameter( " logoutRequest " );
 8 
 9               if  (CommonUtils.isNotBlank(logoutRequest)) {
10 
11                   if  (log.isTraceEnabled()) {
12                      log.trace ( " Logout request=[ "   +  logoutRequest  +   " ] " );
13                  }
14                   // 从xml中解析 SessionIndex key值
15                   final  String sessionIdentifier  =  XmlUtils.getTextForElement(logoutRequest,  " SessionIndex " );
16 
17                   if  (CommonUtils.isNotBlank(sessionIdentifier)) {
18                           // 根据sessionId取得session对象
19                       final  HttpSession session  =  SESSION_MAPPING_STORAGE.removeSessionByMappingId(sessionIdentifier);
20 
21                       if  (session  !=   null ) {
22                          String sessionID  =  session.getId();
23 
24                           if  (log.isDebugEnabled()) {
25                              log.debug ( " Invalidating session [ "   +  sessionID  +   " ] for ST [ "   +  sessionIdentifier  +   " ] " );
26                          }
27                          
28                           try  {
29                   // 让session失效
30                              session.invalidate();
31                          }  catch  ( final  IllegalStateException e) {
32                              log.debug(e,e);
33                          }
34                      }
35                     return ;
36                  }
37              }
38          }  else  { // get方式 表示登录,把session对象放到SESSION_MAPPING_STORAGE(map对象中)
39               final  String artifact  =  request.getParameter( this .artifactParameterName);
40               final  HttpSession session  =  request.getSession();
41              
42               if  (log.isDebugEnabled()  &&  session  !=   null ) {
43                  log.debug( " Storing session identifier for  "   +  session.getId());
44              }
45               if  (CommonUtils.isNotBlank(artifact)) {
46                  SESSION_MAPPING_STORAGE.addSessionById(artifact, session);
47              }
48          }
49 
50          filterChain.doFilter(servletRequest, servletResponse);
51      }

SingleSignOutHttpSessionListener实现了javax.servlet.http.HttpSessionListener接口,用于监听session销毁事件
 1  public   final   class  SingleSignOutHttpSessionListener  implements  HttpSessionListener {
 2 
 3       private  Log log  =  LogFactory.getLog(getClass());
 4 
 5       private  SessionMappingStorage SESSION_MAPPING_STORAGE;
 6      
 7       public   void  sessionCreated( final  HttpSessionEvent event) {
 8           //  nothing to do at the moment
 9      }
10 
11       // session销毁时
12       public   void  sessionDestroyed( final  HttpSessionEvent event) {
13           if  (SESSION_MAPPING_STORAGE  ==   null ) { // 如果为空,创建一个sessionMappingStorage 对象
14              SESSION_MAPPING_STORAGE  =  getSessionMappingStorage();
15          }
16           final  HttpSession session  =  event.getSession(); // 取得当然要销毁的session对象
17          
18           if  (log.isDebugEnabled()) {
19              log.debug( " Removing HttpSession:  "   +  session.getId());
20          }
21           // 从SESSION_MAPPING_STORAGE map根据sessionId移去session对象
22          SESSION_MAPPING_STORAGE.removeBySessionById(session.getId());
23      }
24 
25       /**
26       * Obtains a { @link  SessionMappingStorage} object. Assumes this method will always return the same
27       * instance of the object.  It assumes this because it generally lazily calls the method.
28       * 
29       *  @return  the SessionMappingStorage
30        */
31       protected   static  SessionMappingStorage getSessionMappingStorage() {
32           return  SingleSignOutFilter.getSessionMappingStorage();
33      }
34  }

 服务器端实现


 已经登录的应用会在服务器端保存,所以服务端分别对各个应用发送http请求进行session清除操作。

网上参考资料

看了下面的浏览器cookie变化,会对cas有更深的理解

下载个httpwatch监控一下cookie的变化

客户端消息流程

1.       第一次访问http://localhost:8080/a,

CLIENT:没票据且SESSION中没有消息所以跳转至CAS

CAS:拿不到TGC故要求用户登录

  

2.       认证成功后回跳

CAS:通过TGT生成ST发给客户端,客户端保存TGC,并重定向到http://localhost:8080/a

CLIENT:带有票据所以不跳转只是后台发给CAS验证票据(浏览器中无法看到这一过程)

3.       第一次访问http://localhost:8080/b

CLIENT:没票据且SESSION中没有消息所以跳转至CAS

CAS:从客户端取出TGC,如果TGC有效则给用户ST并后台验证ST,从而SSO。【如果失效重登录或注销时,怎么通知其它系统更新SESSION信息呢??TicketGrantingTicketImpl类grantServiceTicket方法里this.services.put(id,service);可见CAS端已经记录了当前登录的子系统】

4.       再次访问http://localhost:8080/a

CLIENT:没票据但是SESSION中有消息故不跳转也不用发CAS验证票据,允许用户访问

 

 


评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值