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

单点登录应用中,遇到如下的几个问题: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进行了扩展,思路也很简单,主要是将显示默认登录页面的调用,变成了远程调用业务子系统的页面地址,这个地址可以作为配置参数来配置。
  • 0
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
抱歉,作为AI语言模型,我无法为您编写完整的代码。但是,我可以为您提供一些指导,帮助您开始编写基于Vue的SSO单点登录系统。 首先,为了实现SSO单点登录,您需要有一个心认证系统(CAS)来处理用户身份验证和授权。CAS是一个开源项目,具有广泛的社区支持,可以与Vue集成。您可以使用一个现成的CAS服务,例如Apereo CAS,或者自己部署一个CAS服务器。 在Vue实现SSO单点登录的基本步骤如下: 1. 配置CAS客户端 您需要在Vue配置CAS客户端,以便CAS服务器可以向您的应用程序发送认证票据。您可以使用cas-client-vue插件来简化CAS客户端的配置。 2. 编写CAS登录页面 创建一个CAS登录页面,以便用户可以在该页面上输入其凭据并进行身份验证。您可以使用Vue组件来创建登录页面,并使用CAS客户端API来实现登录逻辑。 3. 编写应用程序 创建您的应用程序,并确保将CAS客户端配置正确。您可以使用Vue Router来管理您的应用程序的路由。 4. 集成CAS单点注销 您需要确保当用户注销时,他们的话被正确终止,并且他们被重定向到CAS服务器以进行单点注销。您可以使用cas-client-vue插件来简化单点注销的集成。 总之,实现SSO单点登录需要涉及到多个方面,包括CAS服务器的配置、CAS客户端的集成、登录页面的创建和应用程序的编写。希望这些指导可以帮助您开始编写基于Vue的SSO单点登录系统。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值