`

SSO 之 (单点登录)实施中遇到的几个问题

    博客分类:
  • sso
 
阅读更多
http://blog.csdn.net/yan_dk/article/details/7092094

目录(?)[+]
       单点登录应用中,遇到如下的几个问题:1.超时问题;2.jsessionid问题;3.单点退出时有时子系统未能正常退出;4.有些请求路径不需要单点登录过滤器拦截;5.不同应用服务实现可能要求SSO客户端做适应性改造。我们具体分析一下,并提出解决方法。

1.超时问题
        我们提供的CAS开源单点登录SSO组件,它部署节点主要有2个:SSO服务器(部署内容为一个web应用)、应用系统客户端(部署内容为cas客户端casclient.jar包和相关配置文件)。因此我们根据SSO机制分析一下什么情况下会出现超时。多个应用系统进行SSO集成后,SSO单点登录过程中,登录成功后,应用系统客户端(以下用浏览器客户端为例)的session会保存认证后的用户上下文,SSO服务器会生成一个用户凭证票据(TGT)并缓存起来,浏览器客户端会保存TGC(浏览器cookie中存储的TGT),TGT是作为发放SSO访问服务的票据(ST)的一个凭证票据,发放ST票据后才能正常访问。而浏览器客户端的session会超时(如一般web应用客户端可以设置session的timeout值为30分钟或更长),超时后会让session失效,清空用户上下文,TGC因为仍然是保存在浏览器cookie中,只有关闭浏览器才会清除。SSO服务器端的超时主要是TGT、ST超时,我们一般会设置超时值TGT为2小时,ST为5分钟。关于ST票据使用,一般在首次SSO访问服务时携带着该票据参数,验证票据后能正常访问后,SSO服务器就将此ST销毁失效了;关于TGT票据的使用,一般是正常访问时一直保持为超时时间(2小时),除非做单点退出会销毁TGT。

       基于以上分析,我们可以得出结论,SSO的超时主要涉及2个要素:浏览器的session超时值、TGT的超时值。一般系统设置TGT的超时值>浏览器的session超时值,那么可能有2种超时情况:1.TGT超时(浏览器session超时值小,自然也超时);2.浏览器session超时,TGT不超时。

      第一种“1.TGT超时”,这个处理很简单,用户的有效凭证票据都失效了,自然要重新取得有效凭证票据TGT,需要做的就是重新跳转到登录页面重新登录。

      第二种”2.浏览器session超时,TGT不超时“,这时SSO服务器的TGT票据,以及浏览器客户端的TGC(cookie中的TGT)仍然有效。浏览器客户端再次SSO访问时就可以携带TGC(与服务器的TGT对应),向SSO服务器重新发送取得票据ST请求,取得票据ST后,携带着有效ST票据可以正常访问应用系统了。这个过程是浏览器客户端与SSO服务器的一个通讯交互,用户可能感觉不到,不会出现中断,好像能连续访问,这是为了给用户一个友好的访问体验。明白这个机制,就知道实际上是SSO机制在后台起作用了。


2.jsessionid问题
      jsessionid是java客户端与应用服务器维持session的一个标识,其他语言客户端(如php)有其他标识关键字,具体是什么还不太了解。jsessionid一般存在于浏览器cookie中的(这个一般java客户端连接到应用服务器会自动执行的),一般情况下不会出现在url中,服务器会从客户端的cookie中取出来,但是如果浏览器禁用了cookie的话,就要重写url了,显式的将jsessionid重写到Url中,方便服务器来通过这个找到session的id。CAS开源单点登录SSO组件就提供了这个机制。我研究了CAS源码,基本明白了jsessionid的处理机制。大致原理如下:用户访问业务系统,SSO客户端拦截,重定向到SSO服务器认证时,就将请求路径uri中写入";jsessionid=具体的session值",SSO服务器可以分辨出这个标识值与其他客户端请求不同,进行认证处理,返回的响应给客户端cookie同时也设置了jsessionid的值,之所以在uri和cookie中都设置了jsessionid,是为了双重保障能设置jsessionid值。最后单点登录成功后,返回业务系统访问地址也带有jsessionid参数,这个在uri地址中看起来很别扭。

      提供2种解决方法,如下:

     1) 可以在登录页面地址的请求地址参数中加入参数”&method=POST“(记住这里要求POST大写),这样就可以在最后返回的访问uri中不显示jsessionid。

     2)修改代码如下:

类org.jasig.cas.util.UrlUtils中增加方法cleanupUrl

  

public static final String cleanupUrl(final String url) {                                                                                                                                                        

        if (url == null) {

            return null;

        }

         final int jsessionPosition = url.indexOf(";jsession");

         if (jsessionPosition == -1) {

            return url;

        }

         final int questionMarkPosition = url.indexOf("?");

         if (questionMarkPosition < jsessionPosition) {

            return url.substring(0, url.indexOf(";jsession"));

        }

         return url.substring(0, jsessionPosition)

            + url.substring(questionMarkPosition);

    }
类org.jasig.cas.web.flow.DynamicRedirectViewSelector的makeEntrySelection方法中修改如下行

default:

//  return new ExternalRedirect(serviceResponse.getUrl());//注释源码                                                                                                                                                                                                                                          

     return new ExternalRedirect(UrlUtils.cleanupUrl(serviceResponse.getUrl()));//清除url中jsessionid
这样运行后,url路径中的jsessionid就不存在了。
3.单点退出时有时子系统未能正常退出
      我们知道正常情况下,以用户A单点登录系统,正常访问各子系统,然后执行单点退出时,退出成功后一般跳转回到登录页面要求重新登录,这时各已登录的子系统session被销毁,再次以另一个用户B登录进入后,各子系统显示的应当是用户B的数据信息。可是有时却发现有些子系统仍然显示的是用户A的数据信息,这属于偶发现象。现在分析一下产生这种情况可能的原因,找出解决办法。

      若想了解单点退出机制原理,我们可以先看看CAS源码的SSO单点退出实现机制。参见我的博客中 http://blog.csdn.net/yan_dk/article/details/7095091单点登录实现机制的【单点退出】部分。

      我们看一下源码,看到退出时调用类AbstractWebApplicationService的方法logOutOfService(),代码如下

public synchronized boolean logOutOfService(final String sessionIdentifier) {
        if (this.loggedOutAlready) {
            return true;
        }
     
        LOG.debug("Sending logout request for: " + getId());

        final String logoutRequest = "<samlp:LogoutRequest xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" ID=\""
            + GENERATOR.getNewTicketId("LR")
            + "\" Version=\"2.0\" IssueInstant=\"" + SamlUtils.getCurrentDateAndTime()
            + "\"><saml:NameID xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">@NOT_USED@</saml:NameID><samlp:SessionIndex>"
            + sessionIdentifier + "</samlp:SessionIndex></samlp:LogoutRequest>";

        HttpURLConnection connection = null;

        try {
            final URL logoutUrl = new URL(getOriginalUrl());
            final String output = "logoutRequest=" + URLEncoder.encode(logoutRequest, "UTF-8");

            connection = (HttpURLConnection) logoutUrl.openConnection();
            connection.setDoInput(true);
            connection.setDoOutput(true);
            connection.setRequestProperty("Content-Length", ""
                + Integer.toString(output.getBytes().length));
            connection.setRequestProperty("Content-Type",
                "application/x-www-form-urlencoded");
            final DataOutputStream printout = new DataOutputStream(connection
                .getOutputStream());
            printout.writeBytes(output);
            printout.flush();
            printout.close();

            final BufferedReader in = new BufferedReader(new InputStreamReader(connection
                .getInputStream()));

            while (in.readLine() != null) {
                // nothing to do
            }

            return true;
        } catch (final Exception e) {
            return false;
        } finally {
            if (connection != null) {
                connection.disconnect();
            }
            this.loggedOutAlready = true;
        }
    }


我们看到红字部分,在调用发生异常时,代码是直接返回false的,也就是说单点退出发生错误时,SSO服务器并没有做异常处理,直接返回,这样就有可能在出现异常时(比如网络瞬时闪断),虽然系统界面退出后返回登录页面,但是SSO服务器并没有退出处理,没有销毁登录会话,所以就可能出现没有真正退出,仍然显示前一用户的会话信息。这个应该是CAS源码的一个bug,解决方法是在此处积累错误日志,并抛出异常处理。这样应该能解决此问题。修改代码如下:

。。。                                                                                                                                                                                                                              

} catch (final Exception e) {
         LOG.error("--------------Sending logout request for URL: " + getOriginalUrl()+"Network connection failed.");
           throw new Exception(e);
        } finally {

。。。

说明:

4.有些请求路径不需要单点登录过滤器拦截
       业务系统web应用在使用单点登录组件时,有些请求路径不需要单点登录过滤器拦截,比如公共开放的路径,不需要认证都可以自由访问的路径,单点登录过滤器配置的映射路径一般以通配符匹配路径,但要把这些路径单独提取出来,让过滤器不拦截做单点登录处理,就需要对原有过滤器进行扩展改造,才能实现这个功能。

       扩展实现代码如下:

public class CASFilter implements Filter { 

public static enum ResponseType {
        BREAK, GOON, RETURN
    }                                                                                                                                                                             

...

public void doFilter(
        ServletRequest request,
        ServletResponse response,
        FilterChain fc){

。。。

CASReceipt receipt = (CASReceipt) session.getAttribute(CAS_FILTER_RECEIPT);

if (receipt != null && isReceiptAcceptable(receipt)) {
           log.trace("CAS_FILTER_RECEIPT attribute was present and acceptable - passing  request through filter..");
             fc.doFilter(request, response);
             return;
         }else{
             responeType = beforeDoSSOFilter(request, response);
             if(ResponseType.RETURN==responeType){
              return ;
             }else if(ResponseType.BREAK==responeType) {
                 fc.doFilter(request, response);
                 return;
             }  //else go on
         }



//过滤器的前置处理

public ResponseType beforeDoSSOFilter(ServletRequest request,
   ServletResponse response) {
  return ResponseType.GOON;

}

}



注:主要看原CASFilter 类红字部分扩展代码。

扩展实现类BMCASFilter

package com.sitechasia.sso.bmext;

public class BMCASFilter extends CASFilter {                                                                                                                                                                                                                                               
private final Log log = LogFactory.getLog(this.getClass());
    private static String ssoclient_passedPathSet;//设置不被sso过滤器拦截的请求路径,需要符合url路径通配符,多个路径可以","分割
public static final String PASSEDPATHSET_INIT_PARAM="passedPathSet";//web.xml配置文件中的参数
@Override
public void init(FilterConfig config) throws ServletException {
  super.init(config);
  ssoclient_passedPathSet = SSOClientPropertiesSingleton.getInstance().getProperty(ClientConstants.SSOCLIENT_PASSEDPATHSET)==null?config.getInitParameter(PASSEDPATHSET_INIT_PARAM):SSOClientPropertiesSingleton.getInstance().getProperty(ClientConstants.SSOCLIENT_PASSEDPATHSET);
}
   
    @Override
public ResponseType beforeDoSSOFilter(ServletRequest request,
   ServletResponse response) {
    if (ssoclient_passedPathSet != null) {//路径过滤
     HttpServletRequest httpRequest =(HttpServletRequest)request;
           String requestPath = httpRequest.getRequestURI();
//           String ls_requestPath = UrlUtils.buildFullRequestUrl(httpRequest.getScheme(), httpRequest.getServerName(), httpRequest.getServerPort(), requestPath, null);
          
        PathMatcher  matcher = new AntPathMatcher();
        String passedPaths[]=null;
        passedPaths =ssoclient_passedPathSet.split(",");
       
        if(passedPaths!=null){
         boolean flag;
         for (String passedPath : passedPaths) {
          flag = matcher.match(passedPath, requestPath);//ls_requestPath
                if(flag){
                      log.info("sso client request path '"+requestPath+"'is matched,filter chain will be continued.");
                  return ResponseType.BREAK;
                }
      }
        }
    }
  return ResponseType.GOON;
}


}

web.xml文件中配置修改如下:

<filter>
  <description>单点登陆请求过滤器</description>
  <filter-name>CASFilter</filter-name>
  <filter-class>com.sitechasia.sso.dmext.filter.DMCASFilter</filter-class>

...

<init-param>
   <description>排除路径</description>
   <param-name>passedPathSet</param-name>
   <param-value>
    /**/restful/userLogin/findPassword,
    /**/restful/userLogin/findIllegalLoginCount,
    /**/restful/tenantManager/**,
    /**/restful/lock/**,
    /**/restful/export/**
   </param-value>
  </init-param>
</filter>

<filter-mapping>
        <filter-name>CASFilter</filter-name>
        <url-pattern>/index.jsp</url-pattern>
</filter-mapping>

。。。

注:红字部分为相应配置内容扩展部分

      经过上述这样扩展,配置的排除路径作为请求时,单点登录过滤拦截就会忽略处理,实现了目标功能要求。

5.不同应用服务实现要求SSO客户端做适应性改造
          不同应用服务,对请求的处理方式不同,SSO客户端常规方法不能处理的需要进行适应性改造。我们先看一下SSO客户端常规方法处理请求,web应用中定义一个过滤器casfilter,并配置对安全资源拦截,首次登录系统、或者访问超时时,安全资源的请求被过滤器casfilter拦截,将安全资源路径包装为service参数,并重定向(http状态为302)到SSO服务器地址进行认证,输入凭证信息(用户名、密码等),SSO服务器经过认证、验证票据机制处理,认证成功后,登录通过后继续访问安全资源,再次访问安全资源时,过滤器casfilter拦截,根据用户上下文判断用户已经登录,就不再拦截安全资源,直接正常访问安全资源。而有些应用中使用其他方式处理请求,按上述常规方法不好处理,下面举实例来看看解决方案。

5.1.Ajax客户端
         公司有一个应用系统使用Ajax客户端来处理请求并取得响应,在访问超时时,Ajax客户端请求由于使用异步处理技术(只局部刷新页面),不能将请求继续302重定向到SSO服务器重新认证处理,不能继续访问安全资源,界面响应处于一直延迟状态,很不友好。我们针对此应用进行适应性改造。改造要点如下:

我们约定应用系统的一个Ajax请求的标记(如ajaxRequestFlag=ajaxRequest),在安全资源ajax调用请求时携带此请求参数,还约定一个超时请求的响应状态值(如555),SSO客户端的过滤器casfilter的相应类程序对此约定进行了相应的改造。
应用系统的安全资源页面中引入文件(HttpStatusSSO.js)SSO对Axax请求的http状态的处理,其中有超时时的状态处理、判断是否登录、验证票据等的处理,都是通过ajax请求方式来处理的,其中依赖一个验证票据的资源ticketValidate.jsp。
经过上述改造后,应用系统的安全资源发送携带ajax请求标记的请求,在超时请求时,SSO客户端返回约定响应码(555),HttpStatusSSO.js文件判断处理后转发给相应的登录页面重新认证。单点登录的访问也能够正常的实现功能。这样只扩展了SSO客户端组件的实现,向下版本兼容,保证了SSO组件的统一完整性。

上述改造方式是通过实践开发出来的,可能还有更好的方法来改造扩展,今后持续改进吧。

后记:现在官方的CAS源码3.4版本已经支持Ajax请求的客户端,它的实现机制有待于进一步的研究,大家可以参考。

5.2.多域认证
         有时我们遇到,不同子系统的认证实现的数据源可能来自一个用户数据库。我公司就是这种情况,采用Saas模式运营的软件,认证的用户来源于不同的租户或者域,基于这样的情况,我们对SSO进行了扩展,主要提供了认证接口,认证实现上,可以在认证时动态取得租户对应的数据源,对用户进行认证处理。举例如下:



5.3.SSO集中认证登录页面需要在业务子系统中定制
         SSO集中认证的登录页面默认是放在SSO服务器端的,样式也很不好看,用户需要把登录页面放在业务子系统中自行定制,我们对SSO进行了扩展,思路也很简单,主要是将显示默认登录页面的调用,变成了远程调用业务子系统的页面地址,这个地址可以作为配置参数来配置。举例如下
分享到:
评论
1 楼 rockethj8 2015-09-14  
client 㓟有一个参数是可以忽略一些URL 不进行验证登录的,不要重新源码

ignorePattern Defines the url pattern to ignore, when intercepting authentication requests.
我用的是4.0

文档:
https://github.com/Jasig/java-cas-client/blob/master/README.md

相关推荐

    【转载】CAS实现单点登录(SSO)经典完整教程

    在本文中,我们将深入探讨如何利用CAS实现单点登录功能,这将涉及到以下几个关键知识点: 1. **单点登录(SSO)原理**: SSO允许用户在一个应用系统中登录后,无需再次验证身份即可访问其他相互信任的应用系统。...

    CAS单点登录(SSO)完整教程

    单点登录(Single Sign-On,简称SSO)是一种网络用户身份验证的机制,它允许用户在一次登录后,就可以在多个相互独立的应用系统之间自由切换,而无需再次进行身份验证。CAS(Central Authentication Service)是耶鲁...

    windows下进行单点登录配置操作手册

    配置Windows单点登录主要包括以下几个关键步骤: 1. **设置活动目录域服务**:首先,需要确保你的网络环境基于Windows Server的活动目录域服务(Active Directory Domain Services, AD DS)运行。AD DS提供集中式...

    SSO单点登录的PHP实现方法(Laravel框架)

    在Laravel框架中实现SSO单点登录涉及到的PHP知识点可以从以下几个方面阐述: 1. Laravel框架简述:Laravel是一个基于MVC(Model-View-Controller)架构的PHP框架,它通过各种内置特性简化了Web开发流程,如路由...

    sharepoint sso配置和使用

    SharePoint SSO(Single Sign-On,单点登录)是一种安全机制,它允许用户在访问多个应用系统时只需要登录一次,从而提高工作效率并简化安全管理。在SharePoint环境中,SSO是企业级集成的关键部分,尤其在拥有多种...

    单点登录的DEMO和技术文档

    单点登录(Single Sign-On,简称SSO)是一种网络身份验证机制,允许用户在一个系统上登录后,无需重新认证即可访问多个相互独立的应用系统。它极大地提升了用户体验,减少了记忆和输入多个用户名和密码的繁琐,同时...

    DSMP规范sso分册

    《DSMP规范SSO分册》是一份详细阐述单点登录(Single Sign-On,简称SSO)在DSMP(Data Service Management Platform,数据服务平台)规范中的应用和实施指南。SSO是现代企业信息化系统中常见的一种身份认证机制,它...

    使用CAS 在Tomcat 中实现单点登录实例教程,有例子和参考

    理解并掌握以上知识点,将有助于你成功地在Tomcat环境中实施CAS单点登录。这个教程应包含详细的步骤、示例代码以及可能遇到的问题和解决方法,帮助你一步步构建起安全、高效的SSO环境。在实际操作中,可能会遇到各种...

    企业级单点登录实现...................

    在实施 SSO 系统之前,组织需要考虑以下几个重要的业务管理因素: 1. **需求分析**:明确组织对于 SSO 的具体需求,包括哪些应用程序和服务需要集成,以及期望达到的安全性和用户体验水平。 2. **成本效益分析**...

    CAS单点登录CAS4.0.0+.Net Client

    单点登录(SSO)允许用户在一个应用系统中登录后,无需再次认证即可访问其他相互信任的应用系统。在CAS框架下,这个过程涉及到以下几个核心概念: 1. **CAS服务器**:这是整个SSO系统的核心,负责用户的身份验证。...

    基于资源统一管理的三层单点登录模型研究 (2010年)

    为了解决传统单点登录系统在结构复杂性和灵活性上的不足,以及在实际实施中的高成本问题,本文提出了一个三层单点登录模型。该模型的主要特点在于其简单性,便于在中小企业网络环境中快速实施。三层模型的设计包括...

    程序员转正PPT模板

    - **技术选型**:在选择CASserver作为单点登录方案时,可能遇到了版本兼容性问题,需要调整服务器环境。 - **框架整合**:在集成不同框架的过程中遇到了问题,可能是因为不同的框架之间存在依赖关系或者配置冲突。 -...

    Php多域名登陆

    5. **单点登录(Single Sign-On, SSO)**: 若系统复杂,可考虑采用SSO技术,如CAS(Central Authentication Service)或OAuth,实现更高效的身份验证管理。 在提供的文件列表中,"www.site.com"和"www.php7.com"可能...

    CAS CLIENT 3.1.3

    CAS客户端3.1.3是针对企业或组织的IT系统设计的一款重要的身份验证工具,它主要实现了单点登录(Single Sign-On, SSO)功能。SSO是一种让用户在多个相关应用系统间进行无缝切换,只需一次登录即可访问所有资源的技术...

    CAS实现内外网映射访问的解决办法

    在`org.jasig.cas.client.util`包下新增几个工具类,以辅助实现内外网访问的功能。 - `PropertiesUtil`: 用于读取配置文件中的属性值。 - `HttpConnectionUtil`: 处理HTTP连接相关的操作。 - `CustomConfigUtil...

    cas_client_boot_test_demo.

    这个项目可能是为了展示如何在Spring Boot应用程序中整合CAS(Central Authentication Service)客户端,以便实现单点登录(Single Sign-On, SSO)功能。 【描述】中的关键信息表明,这个"cas_client"是基于Spring ...

    贵州省高校图书馆统一身份认证平台建设探讨.pdf

    在技术层面,统一身份认证平台可能涉及单点登录(Single Sign-On, SSO)技术,通过集中式的身份验证机制,用户只需要提供一次凭证就能访问所有关联的系统。 建设统一身份认证平台的过程中,需要注意几个关键问题:...

    IE的cookie机制导致的session问题及解决办法.doc

    在使用JSP开发的Web应用中,开发者可能会遇到一个令人困惑的问题:当用户使用Firefox浏览器时,基于Session的单点登录(Single Sign-On, SSO)功能表现正常;然而,切换至IE6浏览器时,初始阶段一切正常,但进行几次...

    ssoforwordpress

    在实施SSO for WordPress时,开发者或管理员需要考虑以下几个关键知识点: 1. **身份验证协议**:SSO解决方案可能基于OAuth、OpenID Connect或SAML等标准,理解这些协议的工作原理对于配置和集成至关重要。 2. **...

Global site tag (gtag.js) - Google Analytics