單點(diǎn)登錄(Single Sign On)给僵,簡(jiǎn)稱為 SSO毫捣,是比較流行的企業(yè)業(yè)務(wù)整合的解決方案之一。SSO的定義是在多個(gè)應(yīng)用系統(tǒng)中帝际,用戶只需要登錄一次就可以訪問所有相互信任的應(yīng)用系統(tǒng)蔓同。——百度百科
下面要介紹的單點(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ù)頁(yè)面即可恭取。
下面簡(jiǎn)單介紹具體做法,假設(shè):
- 登錄頁(yè)面:
https://login.abc.com/sigin.html
- 博客服務(wù)頁(yè)面:
https://blog.abc.com/index.html
- 博客服務(wù)與鑒權(quán)服務(wù)均部署在內(nèi)網(wǎng)中熄守,通過 Web 服務(wù)器反向代理供客戶端訪問
- 關(guān)于跨域名共享
Cookie
在前一篇文章已經(jīng)介紹蜈垮,在此不再贅述
判斷是否已經(jīng)登錄
當(dāng)首次打開博客服務(wù)頁(yè)面時(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ò)是安全的负间,所以直接將
uid
和token
作為GET
請(qǐng)求參數(shù)進(jìn)行傳遞偶妖。如果對(duì)安全要求性高姜凄,可將該請(qǐng)求修改為POST
請(qǐng)求。
若用戶已登錄趾访,則直接往下執(zhí)行業(yè)務(wù)流程态秧;若用戶未登錄,則跳轉(zhuǎn)到登錄頁(yè)面扼鞋,并在 url
中帶上登錄成功后的回調(diào)頁(yè)面申鱼,比如:
https://login.abc.com/signin.html?redirect=https://blog.abc.com/index.html
執(zhí)行登錄
跳轉(zhuǎn)到登錄頁(yè)面后,填寫用戶名云头、密碼后向鑒權(quán)服務(wù)發(fā)起登錄請(qǐng)求捐友,鑒權(quán)服務(wù)執(zhí)行登錄驗(yàn)證,若驗(yàn)證通過溃槐,則向客戶端返回登錄成功的信息匣砖,同時(shí)將 uid
和 token
寫入 Cookie
返回給客戶端供后續(xù)鑒權(quán)使用,然后客戶端跳轉(zhuǎn)到登錄前頁(yè)面昏滴;若驗(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));