.net 單點(diǎn)登錄的設(shè)計(jì)與實(shí)踐
前言
最近輪到我在小組晨會(huì)來(lái)分享知識(shí)點(diǎn),突然想到單點(diǎn)登錄,準(zhǔn)備來(lái)分享下如何實(shí)現(xiàn)單點(diǎn)登錄,所以有了下文。實(shí)現(xiàn)方案以及代碼可能寫得不是很嚴(yán)謹(jǐn),有漏洞的地方或者錯(cuò)誤的地方歡迎大家指正。
剛開(kāi)始頭腦中沒(méi)有思路,直接在博客園里面看看別人是如何來(lái)實(shí)現(xiàn)的,看了幾篇文章發(fā)現(xiàn),發(fā)現(xiàn)解決方案有點(diǎn)問(wèn)題,或者說(shuō)不算實(shí)現(xiàn)了單點(diǎn)登錄
名稱定義
為了方便說(shuō)明先說(shuō)明幾個(gè)文中出現(xiàn)的名詞的含義:
P站:統(tǒng)一登錄授權(quán)驗(yàn)證中心,demo中 域名是www.passport.com:801
A站:處于不同域名下的測(cè)試網(wǎng)站,demo中 域名是www.a.com:802
B站:處于不同域名下的測(cè)試網(wǎng)站,demo中 域名是www.b.com:803
Token:用戶訪問(wèn)P站的秘鑰
Ticket:用來(lái)保存用戶信息的加密字符串
單點(diǎn)登錄
訪問(wèn)A站需要登陸的就跳轉(zhuǎn)P站中進(jìn)行登陸,P站登陸之后跳轉(zhuǎn)回至A站,用戶再次訪問(wèn)B站需要登陸的頁(yè)面,用戶不需要進(jìn)行登陸操作就可以正常訪問(wèn)。
實(shí)現(xiàn)思路
未登錄用戶訪問(wèn)A站,首先會(huì)重定向跳轉(zhuǎn)至P站授權(quán)中心,P站首先通過(guò)檢測(cè)Cookie來(lái)判斷當(dāng)前不是處于登陸狀態(tài),就跳轉(zhuǎn)至登陸頁(yè)面進(jìn)行登陸操作,登陸成功之后把用戶信息加密ticket附在A的請(qǐng)求地址上返回,A站通過(guò)解密ticket來(lái)獲取用戶信息,解密成功并存進(jìn)Session中(這樣用戶在A中就處于登陸狀態(tài)了),訪問(wèn)通過(guò);當(dāng)用戶再次訪問(wèn)B站的時(shí)候,對(duì)于B站來(lái)說(shuō),用戶是處于未登錄狀態(tài),則同樣會(huì)重定向跳轉(zhuǎn)至P站授權(quán)中心,P站檢測(cè)Cookie,判斷當(dāng)前用戶處于登陸狀態(tài),就把當(dāng)前用戶信息加密成ticket附在B的請(qǐng)求地址上返回,后面的操作就和A站處理一樣;這樣都登陸之后再次訪問(wèn)A或者B,A和B中Session中都存儲(chǔ)了用戶信息,就不會(huì)再次請(qǐng)求P站了。
簡(jiǎn)單關(guān)系圖
泳道流程圖
主要邏輯說(shuō)明
A站主要邏輯
用戶首先訪問(wèn)A站,A站中會(huì)生成Token,并存入Cache中。Token是A訪問(wèn)P的鑰匙,P在回調(diào)給A的時(shí)候需要攜帶這個(gè)Token。A請(qǐng)求P,P驗(yàn)證Token,P回調(diào)A,A檢測(cè)Token是否是發(fā)送出去的Token,驗(yàn)證之后Token即失效,防止Token被再次使用。
Token的生成是通過(guò)取時(shí)間戳的不同字段進(jìn)行MD5加密生成,當(dāng)然這里可以再加個(gè)鹽進(jìn)行防偽。
/// <summary> /// 生成秘鑰 /// </summary> /// <param name="timestamp"></param> /// <returns></returns> public static string CreateToken(DateTime timestamp) { StringBuilder securityKey = new StringBuilder(MD5Encypt(timestamp.ToString("yyyy"))); securityKey.Append(MD5Encypt(timestamp.ToString("MM"))); securityKey.Append(MD5Encypt(timestamp.ToString("dd"))); securityKey.Append(MD5Encypt(timestamp.ToString("HH"))); securityKey.Append(MD5Encypt(timestamp.ToString("mm"))); securityKey.Append(MD5Encypt(timestamp.ToString("ss"))); return MD5Encypt(securityKey.ToString()); }
P回調(diào)A的時(shí)候進(jìn)行,A中對(duì)Token進(jìn)行校驗(yàn),校驗(yàn)不成功則請(qǐng)求P站統(tǒng)一授權(quán)驗(yàn)證。
/// <summary> /// 授權(quán)枚舉 /// </summary> public enum AuthCodeEnum { Public = 1, Login = 2 } /// <summary> /// 授權(quán)過(guò)濾器 /// </summary> public class AuthAttribute : ActionFilterAttribute { /// <summary> /// 權(quán)限代碼 /// </summary> public AuthCodeEnum Code { get; set; } /// <summary> /// 驗(yàn)證權(quán)限 /// </summary> /// <param name="filterContext"></param> public override void OnActionExecuting(ActionExecutingContext filterContext) { var request = filterContext.HttpContext.Request; var session = filterContext.HttpContext.Session; //如果存在身份信息 if (Common.CurrentUser == null) { if (Code == AuthCodeEnum.Public) { return; } string reqToken = request["Token"]; string ticket = request["Ticket"]; Cache cache = HttpContext.Current.Cache; //沒(méi)有獲取到Token或者Token驗(yàn)證不通過(guò)或者沒(méi)有取到從P回調(diào)的ticket 都進(jìn)行再次請(qǐng)求P TokenModel tokenModel= cache.Get(ConstantHelper.TOKEN_KEY)==null?null:(TokenModel)cache.Get(ConstantHelper.TOKEN_KEY); if (string.IsNullOrEmpty(reqToken) || tokenModel == null || tokenModel.Token!= reqToken || string.IsNullOrEmpty(ticket)) { DateTime timestamp = DateTime.Now; string returnUrl = request.Url.AbsoluteUri; tokenModel = new TokenModel { TimeStamp = timestamp, Token = AuthernUtil.CreateToken(timestamp) }; //Token加入緩存中,設(shè)計(jì)過(guò)期時(shí)間為20分鐘 cache.Add(ConstantHelper.TOKEN_KEY, tokenModel, null, DateTime.Now.AddMinutes(20),Cache.NoSlidingExpiration,CacheItemPriority.Default, null); filterContext.Result = new ContentResult { Content = GetAuthernScript(AuthernUtil.GetAutherUrl(tokenModel.Token, timestamp), returnUrl) }; return; } LoginService service = new LoginService(); var userinfo = service.GetUserInfo(ticket); session[ConstantHelper.USER_SESSION_KEY] = userinfo; //驗(yàn)證通過(guò),cache中去掉Token,保證每個(gè)token只能使用一次 cache.Remove(ConstantHelper.TOKEN_KEY); } } /// <summary> /// 生成跳轉(zhuǎn)腳本 /// </summary> /// <param name="authernUrl">統(tǒng)一授權(quán)地址</param> /// <param name="returnUrl">回調(diào)地址</param> /// <returns></returns> private string GetAuthernScript(string authernUrl, string returnUrl) { StringBuilder sbScript = new StringBuilder(); sbScript.Append("<script type='text/javascript'>"); sbScript.AppendFormat("window.location.href='{0}&returnUrl=' + encodeURIComponent('{1}');", authernUrl, returnUrl); sbScript.Append("</script>"); return sbScript.ToString(); } }
代碼說(shuō)明:這里為了方便設(shè)置Token的過(guò)期時(shí)間,所以使用Cache來(lái)存取Token,設(shè)定Token的失效時(shí)間為兩分鐘,當(dāng)驗(yàn)證成功則從cache中移除Token。
調(diào)取過(guò)濾器
[Auth(Code = AuthCodeEnum.Login)] public ActionResult Index() { return View(); }
P站主要邏輯
P站收到授權(quán)請(qǐng)求,P站首先通過(guò)Coookie來(lái)判斷是否登陸,未登錄則跳轉(zhuǎn)至登陸頁(yè)面進(jìn)行登陸操作。
/// <summary> /// 授權(quán)登陸驗(yàn)證 /// </summary> /// <returns></returns> [HttpPost] public ActionResult PassportVertify() { var cookie=Request.Cookies[ConstantHelper.USER_COOKIE_KEY]; if (cookie == null ||string.IsNullOrEmpty(cookie.ToString())) { return RedirectToAction("Login", new { ReturnUrl = Request["ReturnUrl"] ,Token= Request["Token"] }); } string userinfo = cookie.ToString(); var success= passportservice.AuthernVertify(Request["Token"], Convert.ToDateTime(Request["TimeStamp"])); if (!success) { return RedirectToAction("Login", new { ReturnUrl = Request["ReturnUrl"], Token = Request["Token"] }); } return Redirect(passportservice.GetReturnUrl(userinfo, Request["Token"],Request["ReturnUrl"])); }
已登陸則驗(yàn)證Token
/// <summary> /// 驗(yàn)證令牌 /// </summary> /// <param name="token">令牌</param> /// <param name="timestamp">時(shí)間戳</param> /// <returns></returns> public bool AuthernVertify(string token,DateTime timestamp) { return AuthernUtil.CreateToken(timestamp) == token; }
測(cè)試說(shuō)明
1、修改host
127.0.0.1 www.passport.com
127.0.0.1 www.a.com
127.0.0.1 www.b.com
2、部署IIS
P www.passport.com:801
A www.a.com:802
B www.b.com:803
3、測(cè)試賬號(hào)和webconfig
<add key="PassportCenterUrl" value="http://www.passport.com:801"/>
用戶名:admin 密碼:123
demo
下載地址:源碼下載地址
原文鏈接:http://www.cnblogs.com/minesnil-forfaith/p/6062943.html
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
GridView導(dǎo)出Excel實(shí)現(xiàn)原理與代碼
使用GridView來(lái)展示數(shù)據(jù)庫(kù)表,幾乎沒(méi)對(duì)GridView的格式做什么設(shè)定,從配置文件中加載SQL,跑出數(shù)據(jù)就直接綁定到GridView,接下來(lái)介紹導(dǎo)出Excel的功能感興趣的朋友可以參考下2013-01-01asp.net Repeater取得CheckBox選中的某行某個(gè)值的c#寫法
asp.net(c#)利用Repeater取得CheckBox選中行的某個(gè)值的代碼2008-08-08ASP.NET使用WebService實(shí)現(xiàn)天氣預(yù)報(bào)功能
這篇文章主要為大家詳細(xì)介紹了ASP.NET使用WebService實(shí)現(xiàn)天氣預(yù)報(bào)功能的相關(guān)資料,感興趣的小伙伴們可以參考一下2016-08-08.NET調(diào)用控制臺(tái)下生成的exe文件,傳參及獲取返回參數(shù)的思路及代碼
.NET調(diào)用控制臺(tái)下生成的exe文件,傳參及獲取返回參數(shù)的思路及代碼,需要的朋友可以參考一下2013-06-06asp.net?Core中同名服務(wù)注冊(cè)的實(shí)現(xiàn)代碼
Asp.Net?Core中自帶了容器,同時(shí)也可以使用AutoFac替換掉默認(rèn)的容器,以下為兩種方式同名服務(wù)的注冊(cè)實(shí)現(xiàn),對(duì)asp.net?Core服務(wù)注冊(cè)的實(shí)現(xiàn)代碼感興趣的朋友一起看看吧2022-03-03淺談Asp.net Mvc之Action如何傳多個(gè)參數(shù)的方法
本篇文章主要介紹了Asp.net Mvc之Action如何傳多個(gè)參數(shù)的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-08-08.NET性能優(yōu)化ValueStringBuilder拼接字符串使用實(shí)例
這篇文章主要為大家介紹了.NET性能優(yōu)化ValueStringBuilder拼接字符串的使用實(shí)例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06