前言:
首先介紹下項(xiàng)目背景,我經(jīng)手的項(xiàng)目呢,是一個(gè)音樂網(wǎng)站,看上去是一個(gè)網(wǎng)站,實(shí)際上是三個(gè)項(xiàng)目,首先是一個(gè)基于onethink開發(fā)的音樂分享平臺(tái),第二個(gè)是基于客客族的一個(gè)眾包類型的平臺(tái),第三個(gè)是基于ecshop的商城。三個(gè)項(xiàng)目的分別使用三個(gè)二級(jí)域名。各自代碼數(shù)據(jù)庫獨(dú)立。我接到的需求的就是,讓用戶只需一個(gè)賬號(hào)就能在三個(gè)網(wǎng)站中都能使用。就是用上去像一個(gè)網(wǎng)站就對(duì)了。
當(dāng)時(shí)想了很多種思路,我覺得主要的難點(diǎn)在于,網(wǎng)站已經(jīng)是一個(gè)線上運(yùn)營的項(xiàng)目了,已經(jīng)有一定的用戶。各個(gè)項(xiàng)目數(shù)據(jù)表的設(shè)計(jì)也不相同,對(duì)用戶密碼的加密方式也不同。更厲害的是其中有個(gè)項(xiàng)目的用戶表是一張大表,里面有很多字段,記錄著用戶的各種信息。
步驟:
解決分兩步走,無論用什么方法解決,必須實(shí)現(xiàn)的功能就兩個(gè),那就是:
- 單點(diǎn)注冊
- 單點(diǎn)登陸
1.單點(diǎn)注冊
首先,我們拋開老用戶不管,首先新用戶,一來注冊??隙ㄖ恍枰粋€(gè)地方注冊就不用在別的地方注冊了。那么我的思路是關(guān)閉二三站的注冊,將全部的注冊引導(dǎo)至第一站來注冊。當(dāng)?shù)谝徽镜拇a執(zhí)行到注冊成功,寫入數(shù)據(jù)表的步驟時(shí),我添加兩個(gè)curl帶上用戶的注冊信息,訪問我在二三站寫好了的注冊接口
//注冊成功
//注冊成功要調(diào)用一個(gè)大注冊方法
$bigData['username'] = $username;
$bigData['password'] = $password;
$bigData['email'] = $email;
//加入token,提高安全性
$bigData['token']=md5('xxxxxxxx');
$taskUrl = 'http://task.56sing.com?xxxxxxxxxx.php';
$shopUrl = 'http://shop.56sing.com/xxxxxxxxx.php';
//封裝的curl方法,傳入url和數(shù)據(jù)
$this->bigRegister($taskUrl, $bigData);
$this->bigRegister($shopUrl, $bigData);
由于curl是模擬瀏覽器訪問url,所以也不存在跨域的問題,前端的控制臺(tái),也沒辦法捕獲這次調(diào)用。我自我感覺還是比較安全的
2站和3站的接口接收到傳來的數(shù)據(jù),直接調(diào)用自己的注冊方法就可完成注冊了。這樣一以來,用戶就可以使用童顏的賬號(hào)密碼登陸三個(gè)站點(diǎn)了。
當(dāng)時(shí)感覺還行啊,感覺這個(gè)思路走的通,所以我就打算繼續(xù)用curl傳賬號(hào)密碼,完成單點(diǎn)登陸。當(dāng)代碼寫好了后,發(fā)現(xiàn)傻逼了。于是我冷靜下來重新思考了的網(wǎng)站的登陸原理。
我開始回憶起來最初學(xué)習(xí)登錄的思路,如下圖

乍一看好像也沒什么大問題,我通過curl將賬號(hào)密碼傳入接口,接口收到賬號(hào)密碼后調(diào)用登陸方法,驗(yàn)證通過的話就寫入session。沒毛病???
這時(shí)候,我又有一個(gè)疑問了,當(dāng)我把賬號(hào)密碼傳入接口,通過驗(yàn)證完成登陸時(shí),我的瀏覽器并沒有訪問2,3站的頁面。那么當(dāng)我去訪問它的頁面時(shí)。網(wǎng)站怎么知道我是我呢?(這個(gè)問題給了我靈感)
最后我將注意力集中到了session身上。我回憶起來,其實(shí)每個(gè)session在生成的時(shí)候,都會(huì)一個(gè)session_id存入cookie里。
- 這個(gè)session_id就像是數(shù)組的鍵一樣,有一個(gè)對(duì)應(yīng)的值,這個(gè)值就存在服務(wù)器的內(nèi)存中
那么登陸的原理就是:
我們結(jié)合http協(xié)議來看這個(gè)問題,
當(dāng)我們向服務(wù)器發(fā)起請求,并帶上賬號(hào)密碼,服務(wù)器接收到了賬號(hào)密碼,并驗(yàn)證。如果通過,就把登陸狀態(tài)的值記錄在session中,然后返回一個(gè)登陸成功的頁面或者一個(gè)json。就在返回的時(shí)候攜帶了session_id 存入用戶的瀏覽器中。這樣,這個(gè)session登陸信息就只有剛剛登錄的那個(gè)人的瀏覽器才能訪問,就知道是誰登錄,誰沒登錄了。
那么弄清了原理之后我們就想了一個(gè)思路(是一個(gè)二逼思路)

好了大家看一看就行了哈。我就不介紹了。
經(jīng)過一番思考后,和網(wǎng)上查資料,我發(fā)現(xiàn)了一個(gè)問題,curl只是一個(gè)模擬瀏覽器訪問一個(gè)url。并沒有真正的瀏覽器存在。那么也就沒存cookie的說法。那么我又將目光移向了ajax跨域請求
下面是行的通的思路(敲黑板,劃重點(diǎn))

看到這里,細(xì)心的朋友肯定發(fā)現(xiàn)了,我說這么多,還是沒有說怎么搞定cookie的session_id嘛
下面介紹如何共享session_id的,我們從兩個(gè)方面來說
1. 瀏覽器端(客戶端)
當(dāng)我發(fā)送加密賬號(hào)密碼時(shí),我會(huì)帶上當(dāng)前瀏覽器中儲(chǔ)存的cookie
$.ajax({
//登錄接口的url
url:data.loginData.task.url,
type: 'POST',
//異步請求
async:true,
//通過設(shè)置 withCredentials: true ,發(fā)送Ajax時(shí),Request header中便會(huì)帶上 Cookie 信息
xhrFields:{
withCredentials:true
},
//賬號(hào)密碼
data:{
'sing56':data.loginData.task.sing56,
'sing':data.loginData.task.sing
},
success:function (response) {
},
error:function (reason) {
console.dir(reason);
}
});
2. 服務(wù)器端
首先我們要解決允許跨域請求,大家都是知道的,隨后能就要允許攜帶cookie了
//獲取請求來源地址
$origin = isset($_SERVER['HTTP_ORIGIN'])? $_SERVER['HTTP_ORIGIN'] : '';
//設(shè)置允許請求的數(shù)組
$arrAllow=['http://www.56sing.com','http://shop.56sing.com'];
//判斷是否在允許請求數(shù)組中
if (in_array($origin,$arrAllow)) {
//允許請求
header('Access-Control-Allow-Origin:'.$origin);
}
//允許請求的方式為post
header('Access-Control-Allow-Methods:POST');
//允許攜帶cookie
header('Access-Control-Allow-Credentials:true');
header('Access-Control-Allow-Headers:x-requested-with,content-type');
對(duì)應(yīng)客戶端的 xhrFields.withCredentials: true 參數(shù)
服務(wù)器端通過在響應(yīng) header 中設(shè)置 Access-Control-Allow-Credentials = true 來運(yùn)行客戶端攜帶證書式訪問。通過對(duì) Credentials 參數(shù)的設(shè)置,就可以保持跨域 Ajax 時(shí)的 Cookie。
這里需要注意的是:
服務(wù)器端 Access-Control-Allow-Credentials = true時(shí),參數(shù)Access-Control-Allow-Origin 的值不能為 '*'
那么這樣做的效果就是
當(dāng)我在第一站登錄,調(diào)用2站的登陸接口時(shí)會(huì)傳遞一個(gè)session_id

當(dāng)我進(jìn)入第二站時(shí),賬號(hào)已經(jīng)自動(dòng)登陸,并且當(dāng)前cookie中存儲(chǔ)的session_id,正是我們傳遞過來的那一個(gè)

好了,本次單點(diǎn)登陸實(shí)戰(zhàn)的介紹了就寫到這里了,如果有什么地方不對(duì),希望大神指正.謝謝。
參考三位大神的博客,在這感謝了:
kangjianrong
sueris
guodengh