如何實(shí)現(xiàn)單點(diǎn)登錄?

單點(diǎn)登錄(Single Sign On),簡稱為 SSO,是比較流行的企業(yè)業(yè)務(wù)整合的解決方案之一。SSO的定義是在多個(gè)應(yīng)用系統(tǒng)中,用戶只需要登錄一次就可以訪問所有相互信任的應(yīng)用系統(tǒng)?!俣劝倏?/p>

下面要介紹的單點(diǎn)登錄的實(shí)現(xiàn)方式是我在業(yè)余項(xiàng)目中自己摸索出來的,可能跟主流的實(shí)現(xiàn)方式不一樣,僅做參考。

假設(shè)我現(xiàn)在有一個(gè)博客服務(wù)和一個(gè)圖片服務(wù),我希望只用登錄一次就能使用這兩個(gè)服務(wù)。按照傳統(tǒng)的開發(fā)方式,博客服務(wù)自己帶一個(gè)登錄模塊,圖片服務(wù)自己再帶一個(gè)登錄模塊,加大了工作量不說,用戶體驗(yàn)也很不好。

那么能不能將登錄模塊剝離出來成為一個(gè)獨(dú)立的鑒權(quán)服務(wù)呢?答案是可以。

鑒權(quán)服務(wù)負(fù)責(zé)用戶的注冊(cè)、登錄、注銷等功能,其他服務(wù)需要鑒權(quán)時(shí)只需跳轉(zhuǎn)到鑒權(quán)服務(wù),完成鑒權(quán)后再跳轉(zhuǎn)回服務(wù)頁面即可。

下面簡單介紹具體做法,假設(shè):

  1. 登錄頁面:https://login.abc.com/sigin.html
  2. 博客服務(wù)頁面:https://blog.abc.com/index.html
  3. 博客服務(wù)與鑒權(quán)服務(wù)均部署在內(nèi)網(wǎng)中,通過 Web 服務(wù)器反向代理供客戶端訪問
  4. 關(guān)于跨域名共享 Cookie前一篇文章已經(jīng)介紹,在此不再贅述

判斷是否已經(jīng)登錄

當(dāng)首次打開博客服務(wù)頁面時(shí),博客后臺(tái)服務(wù)會(huì)首先判斷用戶是否登錄,關(guān)鍵代碼如下:

var token = Request.Cookies["token"];
var uid = Request.Cookies["uid"];

if (string.IsNullOrEmpty(uid) || string.IsNullOrEmpty(token))
{
    msg = "非法操作";
    return false;
}
HttpWebRequest request = WebRequest.CreateHttp($"http://localhost:5000/api/Session/{uid}/{token}");
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
using (var sr = new StreamReader(response.GetResponseStream()))
{
    var json = sr.ReadToEnd();
    var result = JsonConvert.DeserializeObject<RequestResult>(json);
    if (result.Code == 200)
    {
        return true;
    }
    msg = result.Msg;
    return false;
}

這里假設(shè)鑒權(quán)服務(wù)和博客服務(wù)均部署在內(nèi)網(wǎng)的同一臺(tái)服務(wù)器上,若兩者不在同一臺(tái)服務(wù)器,請(qǐng)修改 localhost 為鑒權(quán)服務(wù)的內(nèi)網(wǎng) IP 即可。

同樣,由于鑒權(quán)服務(wù)部署于內(nèi)網(wǎng),因此預(yù)先假設(shè)內(nèi)部網(wǎng)絡(luò)是安全的,所以直接將 uidtoken 作為 GET 請(qǐng)求參數(shù)進(jìn)行傳遞。如果對(duì)安全要求性高,可將該請(qǐng)求修改為 POST 請(qǐng)求。

若用戶已登錄,則直接往下執(zhí)行業(yè)務(wù)流程;若用戶未登錄,則跳轉(zhuǎn)到登錄頁面,并在 url 中帶上登錄成功后的回調(diào)頁面,比如:
https://login.abc.com/signin.html?redirect=https://blog.abc.com/index.html

執(zhí)行登錄

跳轉(zhuǎn)到登錄頁面后,填寫用戶名、密碼后向鑒權(quán)服務(wù)發(fā)起登錄請(qǐng)求,鑒權(quán)服務(wù)執(zhí)行登錄驗(yàn)證,若驗(yàn)證通過,則向客戶端返回登錄成功的信息,同時(shí)將 uidtoken 寫入 Cookie 返回給客戶端供后續(xù)鑒權(quán)使用,然后客戶端跳轉(zhuǎn)到登錄前頁面;若驗(yàn)證失敗,則向客戶端返回登錄失敗信息。

var cookieOptions = new CookieOptions
{
    HttpOnly = true,
    Secure = false,
    Domain = "abc.com",
    Path = "/",
    Expires = DateTime.Now.AddDays(7)
};
Response.Cookies.Append("uid", user.Uid, cookieOptions);
Response.Cookies.Append("token", user.Token, cookieOptions);

保持登錄狀態(tài)

為了保持登錄狀態(tài),可在每次請(qǐng)求時(shí)刷新 token 的時(shí)間戳。

退出登錄

客戶端發(fā)起注銷請(qǐng)求:

$.ajax({
    type: 'DELETE',
    url: 'https://login.abc.com/api/Session',
    xhrFields: { withCredentials: true }, // 發(fā)送憑據(jù)
    dataType: "json",
    success: function (response) {
    },
    error: function (error) {
    }
});

服務(wù)端收到注銷請(qǐng)求后,清理登錄信息:

var uid = Request.Cookies["uid"];
var token = Request.Cookies["token"];
if (!Utils.IsLogon(_context, uid, token, _tokenExpiredTimeMin, out string msg))
{
    return Ok(new RequestResult(StatusCodes.Status401Unauthorized, msg));
}

var user = await _context.Userinfo.FindAsync(uid);
if (user == null)
{
    return Ok(new RequestResult(StatusCodes.Status404NotFound, "用戶不存在"));
}

user.Token = null;
user.Tokenrefreshtime = null;

_context.Entry(user).State = EntityState.Modified;

await _context.SaveChangesAsync();

Response.Cookies.Delete("uid");
Response.Cookies.Delete("token");

return Ok(new RequestResult(StatusCodes.Status200OK, null));
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容