.net 單點(diǎn)登錄的設(shè)計(jì)與實(shí)踐
前言
最近輪到我在小組晨會(huì)來分享知識(shí)點(diǎn),突然想到單點(diǎn)登錄,準(zhǔn)備來分享下如何實(shí)現(xiàn)單點(diǎn)登錄,所以有了下文。實(shí)現(xiàn)方案以及代碼可能寫得不是很嚴(yán)謹(jǐn),有漏洞的地方或者錯(cuò)誤的地方歡迎大家指正?! ?/p>
剛開始頭腦中沒有思路,直接在博客園里面看看別人是如何來實(shí)現(xiàn)的,看了幾篇文章發(fā)現(xiàn),發(fā)現(xiàn)解決方案有點(diǎn)問題,或者說不算實(shí)現(xiàn)了單點(diǎn)登錄
名稱定義
為了方便說明先說明幾個(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:用戶訪問P站的秘鑰
Ticket:用來保存用戶信息的加密字符串
單點(diǎn)登錄
訪問A站需要登陸的就跳轉(zhuǎn)P站中進(jìn)行登陸,P站登陸之后跳轉(zhuǎn)回至A站,用戶再次訪問B站需要登陸的頁面,用戶不需要進(jìn)行登陸操作就可以正常訪問。
實(shí)現(xiàn)思路
未登錄用戶訪問A站,首先會(huì)重定向跳轉(zhuǎn)至P站授權(quán)中心,P站首先通過檢測(cè)Cookie來判斷當(dāng)前不是處于登陸狀態(tài),就跳轉(zhuǎn)至登陸頁面進(jìn)行登陸操作,登陸成功之后把用戶信息加密ticket附在A的請(qǐng)求地址上返回,A站通過解密ticket來獲取用戶信息,解密成功并存進(jìn)Session中(這樣用戶在A中就處于登陸狀態(tài)了),訪問通過;當(dāng)用戶再次訪問B站的時(shí)候,對(duì)于B站來說,用戶是處于未登錄狀態(tài),則同樣會(huì)重定向跳轉(zhuǎn)至P站授權(quán)中心,P站檢測(cè)Cookie,判斷當(dāng)前用戶處于登陸狀態(tài),就把當(dāng)前用戶信息加密成ticket附在B的請(qǐng)求地址上返回,后面的操作就和A站處理一樣;這樣都登陸之后再次訪問A或者B,A和B中Session中都存儲(chǔ)了用戶信息,就不會(huì)再次請(qǐng)求P站了。
簡(jiǎn)單關(guān)系圖

泳道流程圖
主要邏輯說明
A站主要邏輯
用戶首先訪問A站,A站中會(huì)生成Token,并存入Cache中。Token是A訪問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的生成是通過取時(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)過濾器
/// </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;
//沒有獲取到Token或者Token驗(yàn)證不通過或者沒有取到從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ì)過期時(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)證通過,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();
}
}
代碼說明:這里為了方便設(shè)置Token的過期時(shí)間,所以使用Cache來存取Token,設(shè)定Token的失效時(shí)間為兩分鐘,當(dāng)驗(yàn)證成功則從cache中移除Token。
調(diào)取過濾器
[Auth(Code = AuthCodeEnum.Login)]
public ActionResult Index()
{
return View();
}
P站主要邏輯
P站收到授權(quán)請(qǐng)求,P站首先通過Coookie來判斷是否登陸,未登錄則跳轉(zhuǎn)至登陸頁面進(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è)試說明
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)文章
MVC文件上傳支持批量上傳拖拽及預(yù)覽文件內(nèi)容校驗(yàn)功能
這篇文章主要介紹了MVC文件上傳支持批量上傳拖拽及預(yù)覽文件內(nèi)容校驗(yàn)功能,需要的朋友可以參考下2017-03-03
ASP.NET Core奇淫技巧之動(dòng)態(tài)WebApi的實(shí)現(xiàn)
這篇文章主要介紹了ASP.NET Core奇淫技巧之動(dòng)態(tài)WebApi的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
解決asp.net上傳文件超過了最大請(qǐng)求長度的問題
下面小編就為大家分享一篇解決asp.net上傳文件超過了最大請(qǐng)求長度的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助2017-11-11
Asp.net(C#)實(shí)現(xiàn)驗(yàn)證碼功能代碼
asp.net驗(yàn)證碼的實(shí)現(xiàn)方法2008-10-10
.NET集成DeveloperSharp實(shí)現(xiàn)高效分頁與無主鍵分頁
DeveloperSharp系列近期又被制造業(yè)ERP、民航飛行App、建筑BIM、電力掌上營業(yè)廳、等多家大型采用,站在巨人的肩膀上你能走的更遠(yuǎn),這篇文章主要介紹了.NET集成DeveloperSharp實(shí)現(xiàn)高效分頁與無主鍵分頁,需要的朋友可以參考下2024-06-06
asp.net 關(guān)于字符串內(nèi)范圍截取的一點(diǎn)方法總結(jié)
前兩天有一位網(wǎng)友提出了一個(gè)字符串內(nèi)截取字符串的問題,除了用普通的字符串截取的方式外,我推薦的是用LINQ方式來截取。兩者實(shí)際上差別不是很大,都是采用字符串截取方式,但后者從寫法和觀察效果會(huì)比前者簡(jiǎn)單實(shí)用得多。2010-02-02
關(guān)于DDD:管理"工作單元實(shí)例"的兩種模式的使用方法
本篇文章介紹了,關(guān)于DDD:管理"工作單元實(shí)例"的兩種模式的使用方法。需要的朋友參考下2013-04-04

