本人qq群也有許多的技術文檔,希望可以為你提供一些幫助(非技術的勿加)。
QQ群: 281442983 (點擊鏈接加入群:http://jq.qq.com/?_wv=1027&k=29LoD19)
多個站點單點登錄的設計思路
一般來說單點認證都需要兩端來完成,在認證中心端的我們稱之為SSO,在網站端的模塊我們稱之為PSO。兩個模塊之間采用二次重定向技術來實現同步兩端票據的方式來實現單點登陸。
為什么需要單點登錄
產品剛上線時,一般由於用戶量少,所有的功能都放在一起,一般也不需要具體的單點登錄。隨着用戶量和業務發展的需要,要求逐步將產品按功能或性能分為相應獨立的站點,並分開部署,這就需要在各個站點之間進行單點登錄,以達到用戶一次登錄,就可以使用多個站點。
單點登錄的實現
簡單方法: 在同一個域內的站點,可以簡單的通過共享Cookie(將登錄用戶名存放Cookie中)來實現單點登錄。這種方法實現簡單,安全性方法可以通過將Cookie值加密方式加強,但對不同域下及不同開發語言下(如A站點C#,B站點C)實現麻煩。
推薦方法:建立統一的認證中心,認證中心提供:
?用戶登錄認證(認證用戶名和密碼),如果成功,返回表示本次登錄的登錄Token
?登錄Token認證(認證Token是否正確),如果成功,返回當前登錄的用戶名
?延長Token有效期
?退出(使Token失效)
認證中心獨立於各個站點,單點流程一般場景如下:
?用戶在站點A輸入用戶名和密碼點擊登錄
?站點A將用戶名和密碼轉發給認證中心進行認證,認證中心返回Token
?站點A將當前登錄用戶和Token存入Session(或Cookie)
?在站點A上點擊連接訪問站點B,通過URL參數方式,將Token帶給站點B
?站點B將Token轉交到認證中心,認證正確,返回當前用戶名。
?站點B將當前登錄用戶和Token存入Session(或Cookie),完成登錄流程
這樣的設計下的Token,還可以用在異步使用Ajax去訪問服務器端的接口(接口可能獨立部署在不同的站點下),這樣只需要帶上Token,服務器端的接口認證Token通過后,直接返回這個登錄用戶的相應用戶信息。
安全性考慮
?用戶登錄認證接口,可增加認證頻率、認證IP等限制,防止暴力攻擊。
?Token其實就是一串表示本次登錄的唯一字符串,可以生成字符串時,增加摘要信息。如Token的組成為:A+MD5(A+PWD) 的方式,A為隨機生成的GUID,這樣在驗證Token時,就可以直接通過算法來驗證合法性,只有算法驗證通過后,再進行下一步的操作。
登陸后產生一個SSO的票據,這個票據是最重要的,因為它是決定用戶是否登陸的關鍵。這個票據可以是Cookie,也可以是Session,我比較傾向於Cookie,因為現在有3DES加密,加密后篡改Cookie幾乎成為不可能,所以無論是對於服務器負擔來說還是安全性都是Cookie比較好,可能人認為萬一不支持Cookie呢,不過我想Demo應該沒問題吧,大不了我設計成兩個都支持。
PS,為什么不用非對稱加密?其實那個效率不高,3DES的安全性已經足夠了,至少現在還沒有人宣稱能破解。
在登陸后就可以通知用戶你已經登陸了,現在你可以去訪問成員站點了,這個時候用戶點擊了成員站點的URL,進去了,這個時候首先就需要接受PSO組件的盤查,你有沒有PSO的票據呢?很顯然是沒有的,所以這個時候請求就被Redirect回了認證中心,認證中心檢查用戶已經有了SSO的票據了,認為用戶已經登錄了,就把用戶的SSO票據附加在URL后邊然后Redirect回成員站點,成員站點的PSO這個時候獲取到了SSO票據,於是知道了用戶已經在認證端登錄了,於是就創建一個PSO票據,然后返回給用戶他所請求的內容。所以我們來看看其實PSO的邏輯更加復雜一點。
我們可以看到其實兩個模塊的功能都不算復雜,這里存在幾個現實的問題,第一個是加密問題,票據需要加密,傳輸的URL也需要加密。
在SSO把票據通過URL發送給PSO的時候,如果我們能夠截獲這個URL,不管他加沒有加密,在下一次我們直接用這個URL去訪問站點的時候因為已經包含SSO票據了,所以PSO會認為已經登陸了而直接產生PSO票據然后就讓用戶進去了,這顯然是一個漏洞。所以呢,我們需要在這里給這個URL加一點鹽值(所謂鹽值其實就是加點料),我們通過在URL里加入時間戳來讓這個URL具備時間限制,這個樣子URL具備失效期,過了這個時間即使截獲到了這個URL也完全沒有作用了。
上面的可能寫的比較亂,看下面的:
一、什么是單點登錄
解釋:登錄一個系統后,其它系統無需再次登錄,即可進入。
二、舉個例子:
你登錄了淘寶,然后你進入天貓,發現你不用登錄了。這時你要注意到,淘寶跟天貓可是完全不一樣的域名。
你登錄淘寶后,你的瀏覽器得到了cookie,但是這個cookie是在淘寶域名下的。你的天貓域名下並沒有cookie。
這個時候你要想辦法讓天貓域名下也有這個相同的cookie。假設你的天貓域名下也有這個cookie。
當你的瀏覽器進入天貓時,你的瀏覽器會攜帶天貓域名下的cookie去服務器驗證。
但是這個cookie對應的可是淘寶域名下的session數據,這個時候又該如何呢。
從這個例子中引出兩個問題:
1、跨域種cookie
2、服務端保持cookie對應的session數據是一樣的
三、解決思路
四、示例代碼
A域名下的 index.php 文件
<?php header("Content-type: text/html; charset=utf-8"); if(!empty($_POST)) { $response = curl_post('http://www.U.com/index.php',$_POST); if($response['code'] > 0) { die('error:'.$response['msg']); } set_cookie('SID',$response['data']['sid'],0,'/','',0,1); echo '登錄成功'; $url = 'http://www.B.com/setcookie.php?sid='.$response['data']['sid']; die('<script type="text/javascript" src="'.$url.'" reload="1"></script>'); } function curl_post( $url , $arrPost = array() , $func = "http_build_query" ){ $ch = curl_init(); $opt[CURLOPT_URL] = $url; $opt[CURLOPT_RETURNTRANSFER] = 1; $opt[CURLOPT_TIMEOUT] = 10; if( !empty( $arrPost ) ){ if( $func == 'json_encode' ) $opt[CURLOPT_HTTPHEADER] = array("Content-Type: application/json;charset=UTF-8"); $opt[CURLOPT_POST] = 1; $opt[CURLOPT_POSTFIELDS] = call_user_func( $func , $arrPost ); } curl_setopt_array ( $ch, $opt ); $response = curl_exec( $ch ); curl_close( $ch ); return json_decode($response, true); } /** * 免刷新寫入cookie * name 必需。規定 cookie 的名稱。 * value 必需。規定 cookie 的值。 * expire 必需。規定 cookie 的有效期。 * path 可選。規定 cookie 的服務器路徑。 * domain 可選。規定 cookie 的域名。 * secure 可選。規定是否通過安全的 HTTPS 連接來傳輸 cookie。 * httponly 可選。規定是否禁止js讀取cookie。 */ function set_cookie($name,$value='',$expire=0,$path='/',$domain='',$secure=0,$httponly=0) { $_COOKIE[$name] = $value; if(is_array($value)){ foreach($value as $k=>$v){ if(is_array($v)){ foreach($v as $a=>$b){ setcookie($name.'['.$k.']['.$a.']',$b,$expire,$path,$domain,$secure,$httponly); } }else{ setcookie($name.'['.$k.']',$v,$expire,$path,$domain,$secure,$httponly); } } }else{ setcookie($name,$value,$expire,$path,$domain,$secure,$httponly); } } ?> <!DOCTYPE HTML> <html> <head> <title>A域名的登錄框</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> </head> <body> <form action="http://www.A.com/index.php" method="post"> <input type="text" name="uname" placeholder="帳號"/> <input type="password" name="upasswd" placeholder="密碼"/> <input type="submit" value="提交" /> </form> </body> </html>
B域名下的 index.php 文件
<?php header("Content-type: text/html; charset=utf-8"); $sid = isset($_COOKIE['SID']) ? $_COOKIE['SID'] : ''; if(empty($sid)) echo "請先登錄"; $response = curl_post('http://www.U.com/index.php',array('sid'=>$sid)); if($response['code'] > 0) { die('error:'.$response['msg']); } die('您的帳號密碼是:'.$response['data']['uinfo']); function curl_post( $url , $arrPost = array() , $func = "http_build_query" ){ $ch = curl_init(); $opt[CURLOPT_URL] = $url; $opt[CURLOPT_RETURNTRANSFER] = 1; $opt[CURLOPT_TIMEOUT] = 10; if( !empty( $arrPost ) ){ if( $func == 'json_encode' ) $opt[CURLOPT_HTTPHEADER] = array("Content-Type: application/json;charset=UTF-8"); $opt[CURLOPT_POST] = 1; $opt[CURLOPT_POSTFIELDS] = call_user_func( $func , $arrPost ); } curl_setopt_array ( $ch, $opt ); $response = curl_exec( $ch ); curl_close( $ch ); return json_decode($response, true); }
B域名下的 setcookie.php 文件
<?php header("Content-type: text/html; charset=utf-8"); $sid = isset($_GET['sid']) ? $_GET['sid'] : ''; if(!empty($sid)) { set_cookie('SID',$sid,0,'/','',0,1); } /** * 免刷新寫入cookie * name 必需。規定 cookie 的名稱。 * value 必需。規定 cookie 的值。 * expire 必需。規定 cookie 的有效期。 * path 可選。規定 cookie 的服務器路徑。 * domain 可選。規定 cookie 的域名。 * secure 可選。規定是否通過安全的 HTTPS 連接來傳輸 cookie。 * httponly 可選。規定是否禁止js讀取cookie。 */ function set_cookie($name,$value='',$expire=0,$path='/',$domain='',$secure=0,$httponly=0) { $_COOKIE[$name] = $value; if(is_array($value)){ foreach($value as $k=>$v){ if(is_array($v)){ foreach($v as $a=>$b){ setcookie($name.'['.$k.']['.$a.']',$b,$expire,$path,$domain,$secure,$httponly); } }else{ setcookie($name.'['.$k.']',$v,$expire,$path,$domain,$secure,$httponly); } } }else{ setcookie($name,$value,$expire,$path,$domain,$secure,$httponly); } }
U域名下的 index.php 文件
<?php header("Content-type: text/html; charset=utf-8"); $sid = isset($_POST['sid']) ? $_POST['sid'] : ''; if(!empty($sid)) { $uname_upasswd = file_get_contents($sid); if(empty($uname_upasswd)) { die(json_encode(array( 'code'=>1, 'msg'=>'fail', 'data'=>array() ))); } die(json_encode(array( 'code'=>0, 'msg'=>'success', 'data'=>array('uinfo'=>$uname_upasswd) ))); } $uname = isset($_POST['uname']) ? $_POST['uname'] : ''; $upasswd = isset($_POST['upasswd']) ? $_POST['upasswd'] : ''; if($uname == '' || $upasswd == '') { die(json_encode(array( 'code'=>1, 'msg'=>'fail', 'data'=>array() ))); } define('SID_SALT', '密碼鹽'); $passwd = passwd($uname.$upasswd,SID_SALT); file_put_contents($passwd, $uname.','.$upasswd); die(json_encode(array( 'code'=>0, 'msg'=>'success', 'data'=>array('sid'=>$passwd) ))); function passwd($string,$salt) { return md5(substr(md5($string).md5($salt),16,48)); }
驗證流程:
1、進入A域名,輸入帳號密碼點擊登錄。
2、進入B域名,此時會打印出你在A域名輸入的帳號密碼。
注意:這只是一個簡單的驗證,實際開發中需要做cookie加密,實效驗證等。而且,其實單點登錄問題,還有其他解決思路。
本人qq群也有許多的技術文檔,希望可以為你提供一些幫助(非技術的勿加)。
QQ群: 281442983 (點擊鏈接加入群:http://jq.qq.com/?_wv=1027&k=29LoD19)
我的淘寶店,可以進去逛逛噢:https://shop108912636.taobao.com/index.htm?spm=2013.1.w5001-7867000954.3.1d29318dPlLar7&scene=taobao_shop