單點登錄相關問題總結


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

[java] view plain copy
  1. <%@ page contentType="text/html; charset=utf-8" %>   
  2. <%       
  3.  response.addHeader("Cache-Control", "no-cache");  
  4.  response.addHeader("Expires", "Thu, 01 Jan 1970 00:00:01 GMT");  
  5.  String ssocookie="www.sso12345678910.com";    
  6. %>   
  7. <mce:script src="http://www.a.com/mp/test/a_setcookie.jsp?id=<%=ssocookie%><!--  
  8. ">   
  9. // --></mce:script>  

Step3:編寫文件 a_setcookie.jsp

[java] view plain copy
  1. <%   
  2.  response.setHeader("P3P","CP=/"CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR/"");   
  3.  String domainId=request.getParameter("id");   
  4.  Cookie _cookie=new Cookie("test",domainId);   
  5.  _cookie.setMaxAge(30*60*100);  
  6.  _cookie.setPath("/");  
  7.    response.addCookie(_cookie);  
  8. %>  

 

Step4:編寫文件 a_getcookie.jsp

[java] view plain copy
  1. <%@ page contentType="text/html; charset=utf-8" %>   
  2. <%      
  3.  Cookie cookies[]=request.getCookies();Cookie sCookie=null;       
  4.  String sname=null;      
  5.  String name=null;      
  6.  if(cookies==null) // 如果沒有任何cookie        
  7.   out.print("none any cookie");  
  8.  else {        
  9.   out.print(cookies.length + "<br>");        
  10.   for(int i=0;i<cookies.length; i++) {          
  11.   sCookie=cookies[i];   
  12.   sname=sCookie.getName();          
  13.   name = sCookie.getValue();      
  14.   out.println("comment==>>>"+sCookie.getComment()+"/n");  
  15.   out.println("getDomain==>>>"+sCookie.getDomain()+"/n");        
  16.   out.println("getSecure==>>"+sCookie.getSecure()+"/n");          
  17.   out.println("getVersion==>>"+sCookie.getVersion()+"/n");          
  18.   out.println("cookiename==>>"+sname + "->" + "cookievalue==>>>"+name + "<br>");  
  19.  }      
  20.  }    
  21. %>  

 

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文件大概如下:

[xhtml] view plain copy
  1. <?xml version="1.0" encoding="UTF-8"?>    
  2. <!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd" >    
  3. <cross-domain-policy>    
  4.     <allow-access-from domain="*" secure="true" />    
  5. </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的內容:

[xhtml] view plain copy
  1. <?xml version="1.0" encoding="UTF-8"?>    
  2. <!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd" >    
  3. <cross-domain-policy>    
  4.     <site-control permitted-cross-domain-policies="all" />    
  5.     <allow-access-from domain="*" />    
  6.     <allow-http-request-headers-from domain="*" headers="*"/>    
  7. </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當中,並不包含該代碼。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM