`

SSO相关问题总结

阅读更多

1 概述
      对于单点登录的机制和原理就不在这里赘述了。本文仅是对于单点登录问题研究所得的心得进行一下总结。
想要实现单点登录可以采用的方式有很多种:
1、 利用成熟的软件框架(CAS,OPENSSO等)
2、自己建设单点登录框架(像sohu的单点登录)
3 、还有就是最简单的使用URL模拟登录。

      但是各种方式都各有利弊。最主要的问题是大部分实现方式都需要对单点登录目标系统进行修改,或者在目标系统中放入单点登录代码。如果我们自己对目标系统没有控制能力,那么与目标系统的沟通就成为最大的阻碍。
      如果可以不修改目标系统,或者可以不在目标系统放入代码就可以登录就是最理想的单点登录。基于这一问题,那么建立模仿登录请求的URL进行登录就成为相对较佳的方案。为什么说是“相对较佳”,因为利用URL进行登录,基本上都有页面的刷新或者是跳转。那么单点登录的设计将成为瓶颈,尤其是在想要设计成无刷新登录或者可以Session保持随时进入目标系统,将给系统带来更多的限制。

      实际上,近期主要是针对这一问题进行了大量的研究,采用了各种方法进行尝试。但是并没有得到实质性的成果,主要存在的问题是:浏览器基于安全考虑所采用的“同源策略”(Same Origin Policy)。

下面我分别根据所采用的方法进行阐述

 

2 跨域解决对策
2.1 Ajax
      Ajax异步发送请求是最先采用的方法。原有的单点登录方式,是在目标系统放置代码。通过URL访问该代码,在由该代码发送Ajax请求进行登录。
      因为该种方式,需要在目标系统放置代码,这样不能满足我们的要求,所以尝试将Ajax发送请求登录代码移到“源系统”(即具备单点登录功能的系统,就是我们的门户系统)。但是请求根本无法发出,浏览器直接就弹出提示框:“该页正在访问其控制范围之外的数据,这有些危险,是否继续"。Ajax本身实际上是通过XMLHttpRequest对象来进行数据的交互,而浏览器出于安全考虑,不允许js代码进行跨域操作,所以会警告。

      有一种方法,用服务器端的XmlHttpRequest代理实现跨域访问。
      我们不能在浏览器端直接使用AJAX来跨域访问资源,但是在服务器端是没有这种跨域安全限制的。所以,只需要让服务器端帮我们完成“跨域访问”的工作,然后在浏览器端用AJAX获取服务器端“跨域访问”的结果就可以了。这就是所谓的在服务器端创建一个XmlHttpRequest代理,通过这个代理来访问其他域名下的资源。这里引用Yahoo! JavaScript Developer Center上的几张图来进一步说明这个方案:

使用XmlHttpRequest访问同一域名下的资源:

使用XmlHttpRequest跨域访问资源:

用服务器端的XmlHttpRequest代理来跨域访问资源:


 编写服务器端XmlHttpRequest代理的具体过程就不赘述了,无非是创建一个自定义的HTTP请求。
 实际上,该方法并没有从本质上解决Ajax的跨域问题。

 

2.2 JavaScript
      比较常用的一种解决跨域的方法是用动态script标签实现客户端的跨域访问 。
      很明显,上一个方案必须要在服务器端做相应的改动才能实现跨域访问。但是很多时候是不能改动服务器端的源代码,那么上一个方案就不能满足需求了。

      我们应该能注意到,虽然浏览器有跨域访问的限制,但是我们是可以通过script标签远程引用其他域名下的脚本文件的。而且,script标签的src属性不一定必须是一个存在的js文件,也可以是一个http handler的url,只要这个http handler返回的是一个text/javascript类型的响应就可以了。

      这样,我们的第二个方案就浮出水面了。这个和上个的区别就是请求是使用<script>标签来请求的,这个要求也是两个域都是由你来开发才行。原理就是JS文件注入,在本域内的a内生成一个JS标签,它的SRC指向请求的另外一个域的某个页面b,b返回数据即可,可以直接返回JS的代码。因为script的src属性是可以跨域的。具体看代码,这个也比较简单。

 

2.3 Iframe
Iframe具体使用情况有:
一、本域和子域的相互访问: www.aa.com 和book.aa.com
二、本域和其他域的相互访问: www.aa.comwww.bb.com 用 iframe

      第一种情况如果想做到数据的交互,那么www.aa.com 和book.aa.com必须由你来开发才可以。可以将book.aa.com用iframe添加到www.aa.com 的某个页面下,在www.aa.com 和iframe里面都加上document.domain = "aa.com",这样就可以统一域了,可以实现跨域访问。就和平时同一个域中镶嵌iframe一样,直接调用里面的JS就可以了。(这个办法我没有尝试,不过理论可行,而且下面的跨子域Cookie方法似乎更好)

      第二种情况当两个域不同时,如果想相互调用,那么同样需要两个域都是由你来开发才可以。用iframe可以实现数据的互相调用。解决方案就是用window.location对象的hash属性。hash属性就是http://domian/web/a.htm#dshakjdhsjka 里面的#dshakjdhsjka。利用JS改变hash值网页不会刷新,可以这样实现通过JS访问hash值来做到通信。不过除了IE之外其他大部分浏览器只要改变hash就会记录历史,你在前进和后退时就需要处理,非常麻烦。不过再做简单的处理时还是可以用的,具体的代码我再下面有下载。大体的过程是页面a和页面b在不同域下,b通过iframe添加到a里,a通过JS修改iframe的hash值,b里面做一个监听(因为JS只能修改hash,数据是否改变只能由b自己来判断),检测到b的hash值被修改了,得到修改的值,经过处理返回a需要的值,再来修改a的hash值(这个地方要注意,如果a本身是那种查询页面的话比如http://domian/web/a.aspx?id=3 ,在b中直接parent.window.location是无法取得数据的,同样报没有权限的错误,需要a把这个传过来,所以也比较麻烦),同样a里面也要做监听,如果hash变化的话就取得返回的数据,再做相应的处理。

 

2.4 HttpClient
      下面所采用的办法是利用Apache的HttpClient进行登录。
      通常,我们使用A系统中的URL进行单点登录的流程是这样的。首先创建模拟A系统中登录表单提交的URL进行登录(我们把这个URL叫做URL1)。如果登录成功的话,用A系统的正常访问URL访问该网站就可以看到已经是登录状态(我们把这个URL叫做URL2)。
      其原理就是HTTP协议的原理,在我们利用URL1进行访问以后,服务器就会为该用户创建一个Session,并在响应中设置“Set-Cookie”头信息,信息中包含对应的SessionID。浏览器就会根据该信息在客户端生成Cookie。当我们再访问URL2时,浏览器就会判断该会话中是否已创建Cookie,如果已经创建就会在请求中添加Cookie头信息,信息中包含对应的SessionID。服务器在处理消息时判断SessionID是否相同,相同就认为是同一会话,同一个人。
这就是现在解决HTTP协议无状态的办法。但是这个前提是在使用同一个浏览器。就像现在的IE7,虽然可能会打开多个Tab页,但是还是同一个浏览器。

      利用HttpClient确实可以模拟发送请求,登录进入目标系统。但是HttpClient的原理是每建立一个链接,相当于新打开一个浏览器。那么按照上面所说流程分别访问URL1和URL2将会被模拟成打开两个浏览器,换句话说在访问URL2时,就不会带上Cookie的消息头,服务器就会认为不是同一个会话,也就不会模拟出成功的登录状态。

根据上面的描述,解决问题的症结就在于:在发送访问URL2的请求时可以带上URL1返回的Cookie信息即可。

想要在访问URL2的时候带上Cookie头信息,有以下几个办法
1、 在访问URL1之后,在客户端创建Cookie
2、 在访问URL2时,在消息头中加入Cookie头信息。
3、 在访问URL2时,进行URL重写,在其后加入Cookie头中的信息。

      通常情况下,以上三种操作都不需要我们自己做,完全可以由服务器和系统自动完成。但是我们所要处理的情况是在B系统中,通过访问A系统的URL1和URL2登录到A系统中,这样就会产生问题。

 

2.5 跨域Cookie
1、从B系统中访问A系统的URL1,出于同源策略的安全考虑,浏览器会禁止A系统生成Cookie。
2、很不幸的是J2EE并没有提供在请求头中加入Cookie头的方法。
3、该方法是唯一可行的,但是如果B系统引用A系统的类似于URL2的URL过多,URL重写是一块加到的工作了。
      总结的来说,就是因为同源策略导致不能共享Cookie,也因此不能维持Session的联通。但是同源策略是支持跨子域Cookie的。

 

2.6 跨子域Cookie
      所谓跨子域登录,A,B站点和P站点位于同一个域下面,比如A站点为http://blog.yizhu2000.com ,B站点为 http://forum.yizhu2000.com ,他们和登录站点P的关系可以看到,都是属于同一个父域,yizhu2000.com,不同的是子域不同,一个为blog,一个为forum,一个是passport我们先看看最常用的非跨站点普通登录的情况,一般登录验证通过后,一般会将你的用户名和一些用户信息,通过某一密钥进行加密,写在本地,也就是一个加密的cookie,我们把这个cookie叫做--票(ticket)。

      需要判断用户是否登录的页面,需要读取这个ticket,并从其中解密出用户信息,如果ticket不存在,或者无法解密,意味着用户没有登录,或者登录信息不正确,这时就要跳转到登录页面进行登录,在这里加密的作用有两个,一是防止用户信息被不怀好意者看到,二是保证ticket不会被伪造,后者其实更为重要,加密后,各个应用需要采用与加密同样的密钥进行解密,如果不知道密钥,就不能伪造出ticket,(注:加密和解密的密钥有可能不同,取决于采用什么加密算法,如果是对称加密,则为同一密钥,如果是非对称,就不同了,一般用私钥加密,公钥解密,但是无论怎样,密钥都只有内部知道,这样伪造者既无法伪造也无法解密ticket)

      跨子域的单点登录,和上述普通登录的过程没有什么不同,唯一不同的是写cookie时,由于登录站点P和应用A处于不同的子域,P站写入的cookie的域为passport.yizhu2000.net,而A站点为forum.yizhu2000.net,A在判断用户登录时无法读到P站点的ticket

      解决方法非常简单,当Login完成后P站点写ticket的时候,只需把cookie的域设为他们共同的父域,yizhu2000.net就可以了:cookie.domain="yizhu2000.net",A站点自然就可以读到这个ticket了

 

2.7 P3P
      在网上查过资料,有一种方法是可以做到不同域的Cookie设置,其方法就是在响应头中加入P3P头信息。设置方法举例如下:

Step1:
      首先在hosts文件中设置(其中的192.168.73.1为您本机的ip,写成127.0.0.1不行。) 只是举例在真正服务器上不需要这样。
192.168.73.1 www.a.com
192.168.73.1 www.b.com

Step2:编写文件 b_setcookie.jsp

view plaincopy to clipboardprint?
<%@ page contentType="text/html; charset=utf-8" %>  
<%      
 response.addHeader("Cache-Control", "no-cache"); 
 response.addHeader("Expires", "Thu, 01 Jan 1970 00:00:01 GMT"); 
 String ssocookie="www.sso12345678910.com ";   
%>  
<mce:script src="http://www.a.com/mp/test/a_setcookie.jsp?id=<%=ssocookie% ><!-- 
">  
// --></mce:script> 
Step3:编写文件 a_setcookie.jsp
view plaincopy to clipboardprint?
<%  
 response.setHeader("P3P","CP=\"CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR\"");  
 String domainId=request.getParameter("id");  
 Cookie _cookie=new Cookie("test",domainId);  
 _cookie.setMaxAge(30*60*100); 
 _cookie.setPath("/"); 
   response.addCookie(_cookie); 
%> 

Step4:编写文件 a_getcookie.jsp
view plaincopy to clipboardprint?
<%@ page contentType="text/html; charset=utf-8" %>  
<%     
 Cookie cookies[]=request.getCookies();Cookie sCookie=null;      
 String sname=null;     
 String name=null;     
 if(cookies==null) // 如果没有任何cookie       
  out.print("none any cookie"); 
 else {       
  out.print(cookies.length + "<br>");       
  for(int i=0;i<cookies.length; i++) {         
  sCookie=cookies[i];  
  sname=sCookie.getName();         
  name = sCookie.getValue();     
  out.println("comment==>>>"+sCookie.getComment()+"\n"); 
  out.println("getDomain==>>>"+sCookie.getDomain()+"\n");       
  out.println("getSecure==>>"+sCookie.getSecure()+"\n");         
  out.println("getVersion==>>"+sCookie.getVersion()+"\n");         
  out.println("cookiename==>>"+sname + "->" + "cookievalue==>>>"+name + "<br>"); 
 }     
 }   
%> 

Step5:测试
依次请求
http://www.b.com/mp/test/b_setcookie.jsp
http://www.a.com/mp/test/a_getcookie.jsp
便可看到通过跨域设置的cookie的值!
      这种方法虽然可以实现跨域设置Cookie,但还是需要在目标系统进行代码的注入。

 

3 题外话
       实际上水晶易表Flash不能跨域访问WebService获取数角也是因为类似于同源策略的安全沙盒问题引起。
 对于安全沙盒问题,倒是也有相应的解决办法。
       如果想要在Flash里面跨域获取数据,就必须在对方server上配置crossdomain.xml。具体来说,比如你的Flash在domain A下面,而你想要访问domain B暴露的web service,那么domain B的server根目录下必须要有一个crossdomain.xml文件来配置说你有这个权限。这个是Flash Player的安全限制。
       对于Flash Player 9之前的版本,这个crossdomain.xml文件大概如下:

view plaincopy to clipboardprint?
<?xml version="1.0" encoding="UTF-8"?>   
<!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd " >   
<cross-domain-policy>   
    <allow-access-from domain="*" secure="true" />   
</cross-domain-policy> 

 
      以上配置允许所有domain访问当前server所暴露的数据(比如web service)。你可以在domain属性里面指定特殊的规则。secure属性用来设置你所暴露的数据是否走https协议。
      但是对于Flash Player 9而言,crossdomain.xml文件内容出现了较大的变化,原因是Flash Player 9的security机制有所改变。所以当我用Flex 3调用cross domain的web service时,还使用上面的crossdomain.xml文件,结果就报错说security error。于是稍微研究了一下,得到如下解决方案,其实就是要改变crossdomain.xml的内容:
view plaincopy to clipboardprint?
<?xml version="1.0" encoding="UTF-8"?>   
<!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd " >   
<cross-domain-policy>   
    <site-control permitted-cross-domain-policies="all" />   
    <allow-access-from domain="*" />   
    <allow-http-request-headers-from domain="*" headers="*"/>   
</cross-domain-policy> 

     以上是Flash Player 9所要求的crossdomain.xml的内容。可以看到多了两个tag。其中site-control是可选的,但是allow-http-request-headers-from对于cross domain的web service确实必须的。如果没有允许header,就会像我之前一样报错。这些配置项的具体含义以及其他可选配置项,可以参考http://www.adobe.com/devnet/flashplayer/articles/flash_player_9_security.pdf
      当然在生成的Flash当中,需要有代码来调用相应的crossdomain.xml。但是水晶易表所导出的Flash当中,并不包含该代码。

 

分享到:
评论

相关推荐

    java8源码-JavaGuide:指南

    操作系统相关概念总结 Linux 数据结构与算法 数据结构 算法 数据库 MySQL Redis 系统设计 常用框架 Spring SpringBoot MyBatis 认证授权 JWT SSO(单点登录) SSO(Single Sign On)即单点登录说的是用户登陆多个子系统...

    java8源码-javaknown:知乎

    操作系统相关概念总结 Linux 数据结构与算法 数据结构 算法 数据库 MySQL Redis 系统设计 常用框架 Spring SpringBoot MyBatis 认证授权 JWT SSO(单点登录) SSO(Single Sign On)即单点登录说的是用户登陆多个子系统...

    remp:REMP-读者参与度和获利平台。 一套开放源代码的工具,供发布者与他们的受众互动。 仓库是我们内部私有仓库的公共镜像

    雷姆 ... 您可以直接从网站上的Javascript跟踪与浏览量相关的事件,也可以从后端调用API。 Beam admin提供了一种在网站上显示实时使用情况统计信息,汇总的文章/作者/转换数据的方法,并允许您基于跟

    基于springboot的web项目最佳实践+源代码+文档说明

    + [总结](#总结) `springboot` 可以说是现在做`javaweb`开发最火的技术,我在基于`springboot`搭建项目的过程中,踩过不少坑,发现整合框架时并非仅仅引入`starter` 那么简单。 要做到简单,易用,扩展性更好,还...

    asp.net知识库

    帮助解决网页和JS文件中的中文编码问题的小工具 慎用const关键字 装箱,拆箱以及反射 动态调用对象的属性和方法——性能和灵活性兼备的方法 消除由try/catch语句带来的warning 微软的应试题完整版(附答案) 一个...

    基于SpringBoot+Hibernate+Shiro的库存管理系统+源代码+文档说明

    2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。...

    PHPDisk E-Core企业网盘系统 v3.0.zip

    [新增]PHPDISK_SSO插件,可以整合支持本插件的系统或第三方系统(需要二次开发整合),同步登录、注册网站用户数据 [新增]浏览器支持HTML5大文件上传 [新增]后台可以设置新用户是否审核才能使用 [新增]后台文件,...

    亮剑.NET深入体验与实战精要2

    本书既考虑到实际开发中经常遇到的困惑和难题,也分析了解决问题的思路和方法,更总结出项目开发中不可或缺的技术点及思想。读者可以在欣赏一个个有趣例子的过程中,不知不觉具备开发真正商业项目的能力。 本书集...

    亮剑.NET深入体验与实战精要3

    本书既考虑到实际开发中经常遇到的困惑和难题,也分析了解决问题的思路和方法,更总结出项目开发中不可或缺的技术点及思想。读者可以在欣赏一个个有趣例子的过程中,不知不觉具备开发真正商业项目的能力。 本书集...

    python入门到高级全栈工程师培训 第3期 附课件代码

    03 用户增删该查及组相关操作 04 对文件的权限管理 05 对目录的权限管理 06 权限管理补充 07 属主属组及基于数字的权限管理 第5章 01 上节课复习 02 文件合并与文件归档 03 文件归档与两种压缩方式 04 vim编辑器 ...

    单点登录源码

    | └── zheng-upms-server -- 用户权限系统及SSO服务端[端口:1111] ├── zheng-cms -- 内容管理系统 | ├── zheng-cms-common -- cms系统公共模块 | ├── zheng-cms-dao -- 代码生成模块,无需开发 | ├──...

Global site tag (gtag.js) - Google Analytics