天天看點

php跨域單點登入,CORS實作跨域SSO單點登入sso

前一段時間看過CAS實作跨域單點登入SSO的方案,覺得比較複雜,想到如果有了CORS是不是可以直接使用跨域ajax帶session實作SSO呢?用PHP試驗了一下,還真的可以。看懂接下來的流程和實作,需要有一些js,html,PHP,ajax CORS跨域的一些相關知識。

CAS的SSO方案本文就不再贅述,想了解的老鐵可以另行查找相關資料,下面是整個實作流程:

流程邏輯圖

php跨域單點登入,CORS實作跨域SSO單點登入sso

image.png

測試環境

有兩個域名(此處為了簡化隻示範一個業務系統,有需要可以添加多個b.com,c.com..邏輯都一樣):

sso.com(單點登入系統)

a.com(業務系統A)

1、a.com首頁代碼

index.php

session_start();

//模拟業務系統登入狀态檢視

if(!empty($_SESSION["isLogin"])){

echo "使用者".$_SESSION["username"]." ,已登入";

}else{

echo "未登入";

}

a.com/index.php,這裡簡化為隻驗證session登入狀态

第一次通路結果是未登入狀态:

php跨域單點登入,CORS實作跨域SSO單點登入sso

1.PNG

2、sso.com前端登入頁面(sso.com/index.html)

一個表單輸入使用者名,一個按鈕觸發登入邏輯

sso

使用者名:

登入

頁面通路:

php跨域單點登入,CORS實作跨域SSO單點登入sso

2.PNG

假設輸入使用者名user, 點選登入按鈕觸發使用者登入邏輯,ajax發送使用者名到sso.com/login.php。

登入邏輯的js代碼如下:

//-------登入Ajax請求 START

let btn = document.getElementById("loginBtn");

btn.addEventListener('click', ()=>{

let xhr = new XMLHttpRequest();

xhr.open('POST', 'login.php');

xhr.setRequestHeader('Content-Type', "application/json");

xhr.onload = function(){

//接收到sso.com/login.php的傳回

let res = JSON.parse(xhr.response);

console.log(res);

//發送跨域驗證token請求到所有業務系統

for(let serverAddr of res.servers){

loginAll(res.token, serverAddr);

}

};

let username = document.getElementById("userInput").value;

let userInfo = {username: username};

xhr.send(JSON.stringify(userInfo))

});

//-------登入Ajax請求 END

先不管loginAll函數,這個是ajax跨域到業務系統的代碼

3、sso.com/login.php接受登入請求的代碼如下:

//此處省略使用者驗證直接登入成功

//.....

//擷取使用者資訊

$userInfo = json_decode(file_get_contents("php://input"), true);

//生成token

$token = genToken();

//存儲token和使用者名

save_user_token($userInfo["username"], $token);

//發送token回前端

echo json_encode([

"token"=>$token,

//業務系統驗證token位址,此處隻示範一個,多個業務系統添加到數組即可

"servers"=>["http://a.com/check.php"]

]);

//寫入token,使用者資訊到存儲,此處用檔案示範,也可用mysql,redis等替代

function save_user_token(string $username, string $token){

$tokenMap = [];

if(file_exists("../test.txt")){

$file_content = file_get_contents("../test.txt");

$tokenMap = unserialize($file_content);

}

$tokenMap[$token] = $username;

file_put_contents("../test.txt", serialize($tokenMap));

}

//生成随機token

function genToken(){

//随機生成10位字元串token

$strs="QWERTYUIOPASDFGHJKLZXCVBNM1234567890qwertyuiopasdfghjklzxcvbnm";

$token=substr(str_shuffle($strs),mt_rand(0,strlen($strs)-11),10);

return $token;

}

這段代碼的作用就是驗證使用者登入(簡化為總是成功),生成一個token,存儲起來,并把token和所有業務系統校驗token位址(這裡簡化為一個http://a.com/check.php)傳回給前端。

浏覽器調試---------

發送的驗證資訊:

php跨域單點登入,CORS實作跨域SSO單點登入sso

image.png

收到的傳回,帶token和業務系統驗證token位址:

php跨域單點登入,CORS實作跨域SSO單點登入sso

image.png

3、sso.com/index.html接收到token,執行ajax跨域登入到業務系統

補充上面缺少的loginAll(res.token, serverAddr);代碼中的loginAll函數:

//跨域登入一個業務系統

function loginAll(token, address){

let xhr = new XMLHttpRequest();

xhr.open('POST', address);

xhr.withCredentials = true;

//這裡Content-Type如果是json會觸發非簡單跨域,此處為了簡化不使用

xhr.setRequestHeader('Content-Type', 'text/plain');

xhr.onload = function(){

// 收到驗證傳回

let str = xhr.response;

console.log(str);

};

let tokenObj = {token: token};

xhr.send(JSON.stringify(tokenObj));

}

業務系統a.com/check.php驗證token的代碼:

//跨域

header("Access-Control-Allow-Origin: http://sso.com");

// 響應類型

header('Access-Control-Allow-Methods:POST,GET');

// 帶 cookie 的跨域通路

header('Access-Control-Allow-Credentials: true');

// 響應頭設定

//header('Access-Control-Allow-Headers:x-requested-with,Content-Type,X-CSRF-Token');

session_start();

//擷取post資料

$tokenInfo = json_decode(file_get_contents("php://input"), true);

//傳回驗證結果

$username = check_token($tokenInfo["token"]);

$res = [];

//驗證成功設定登入session

if($username){

//把使用者資訊和登入表示寫入session

$_SESSION["isLogin"] = true;

$_SESSION["username"] = $username;

$res["res"] = "驗證成功";

}else{

$res["res"] = "驗證失敗";

}

//傳回給sso的前端

echo json_encode($res);

//從存儲中取出token并檢查

function check_token(string $token){

if(!file_exists("../test.txt")){

return false;

}

$file_content = file_get_contents("../test.txt");

$tokenMap = unserialize($file_content);

if(!key_exists($token, $tokenMap)){

return false;

}

return $tokenMap[$token];

}

浏覽器調試:

伺服器傳回帶cookie跨域頭

php跨域單點登入,CORS實作跨域SSO單點登入sso

image.png

發送到業務系統的token

php跨域單點登入,CORS實作跨域SSO單點登入sso

image.png

業務系統傳回結果

php跨域單點登入,CORS實作跨域SSO單點登入sso

image.png

4、直接通路業務系統a.com/index.php

發現已經在登入狀态了,整個sso流程結束

php跨域單點登入,CORS實作跨域SSO單點登入sso

image.png

結束語:

對比傳統的CAS,我覺得有以下一些優點:

步驟更少,邏輯簡單

token不寫入cookie, 隻在前端3步臨時變量存儲,随後釋放,更安全

sso伺服器登入後,再通路其他業務系統,不需要redirect跳轉,使用者體驗好

缺點:必須使用支援CORS跨域的浏覽器通路,如IE10之前的老浏覽器不支援

注意:

本文為原創,代碼可以随意使用,無版權,轉載請注明原位址

本文代碼不能直接用在生産環境,隻用作流程示範,如需使用需要修改增加安全性,考慮登出流程等等

本例公共存儲中,為了示範友善存儲了使用者名,實際使用中可以存儲資料庫使用者表中記錄主鍵id等等,友善業務系統關聯使用者和session。

實際使用中需考慮各個業務系統和sso系統的session過期問題。

本文前端代碼在Chrome 77核心的浏覽器下運作正常,其他浏覽器相容性本文不做探讨