前言
最近輪到我在小組晨會來分享知識點,突然想到單點登錄,准備來分享下如何實現單點登錄,所以有了下文。實現方案以及代碼可能寫得不是很嚴謹,有漏洞的地方或者錯誤的地方歡迎大家指正。
剛開始頭腦中沒有思路,直接在博客園里面看看別人是如何來實現的,看了幾篇文章發現,發現解決方案有點問題,或者說不算實現了單點登錄
名稱定義
為了方便說明先說明幾個文中出現的名詞的含義:
P站:統一登錄授權驗證中心,demo中 域名是www.passport.com:801
A站:處於不同域名下的測試網站,demo中 域名是www.a.com:802
B站:處於不同域名下的測試網站,demo中 域名是www.b.com:803
單點登錄
訪問A站需要登陸的就跳轉P站中進行登陸,P站登陸之后跳轉回至A站,用戶再次訪問B站需要登陸的頁面,用戶不需要進行登陸操作就可以正常訪問。
實現思路
簡單關系圖
泳道流程圖
主要邏輯說明
A站主要邏輯
1 /// <summary> 2 /// 生成秘鑰 3 /// </summary> 4 /// <param name="timestamp"></param> 5 /// <returns></returns> 6 public static string CreateToken(DateTime timestamp) 7 { 8 StringBuilder securityKey = new StringBuilder(MD5Encypt(timestamp.ToString("yyyy"))); 9 securityKey.Append(MD5Encypt(timestamp.ToString("MM"))); 10 securityKey.Append(MD5Encypt(timestamp.ToString("dd"))); 11 securityKey.Append(MD5Encypt(timestamp.ToString("HH"))); 12 securityKey.Append(MD5Encypt(timestamp.ToString("mm"))); 13 securityKey.Append(MD5Encypt(timestamp.ToString("ss"))); 14 return MD5Encypt(securityKey.ToString()); 15 }
P回調A的時候進行,A中對Token進行校驗,校驗不成功則請求P站統一授權驗證。
1 /// <summary> 2 /// 授權枚舉 3 /// </summary> 4 public enum AuthCodeEnum 5 { 6 Public = 1, 7 Login = 2 8 } 9 10 /// <summary> 11 /// 授權過濾器 12 /// </summary> 13 public class AuthAttribute : ActionFilterAttribute 14 { 15 /// <summary> 16 /// 權限代碼 17 /// </summary> 18 public AuthCodeEnum Code { get; set; } 19 20 /// <summary> 21 /// 驗證權限 22 /// </summary> 23 /// <param name="filterContext"></param> 24 public override void OnActionExecuting(ActionExecutingContext filterContext) 25 { 26 var request = filterContext.HttpContext.Request; 27 var session = filterContext.HttpContext.Session; 28 //如果存在身份信息 29 if (Common.CurrentUser == null) 30 { 31 if (Code == AuthCodeEnum.Public) 32 { 33 return; 34 } 35 string reqToken = request["Token"]; 36 string ticket = request["Ticket"]; 37 Cache cache = HttpContext.Current.Cache; 38 //沒有獲取到Token或者Token驗證不通過或者沒有取到從P回調的ticket 都進行再次請求P 39 TokenModel tokenModel= cache.Get(ConstantHelper.TOKEN_KEY)==null?null:(TokenModel)cache.Get(ConstantHelper.TOKEN_KEY); 40 if (string.IsNullOrEmpty(reqToken) || tokenModel == null || tokenModel.Token!= reqToken || 41 string.IsNullOrEmpty(ticket)) 42 { 43 DateTime timestamp = DateTime.Now; 44 string returnUrl = request.Url.AbsoluteUri; 45 tokenModel = new TokenModel 46 { 47 TimeStamp = timestamp, 48 Token = AuthernUtil.CreateToken(timestamp) 49 }; 50 //Token加入緩存中,設計過期時間為20分鍾 51 cache.Add(ConstantHelper.TOKEN_KEY, tokenModel, null, DateTime.Now.AddMinutes(20),Cache.NoSlidingExpiration,CacheItemPriority.Default, null); 52 filterContext.Result = new ContentResult 53 { 54 Content = GetAuthernScript(AuthernUtil.GetAutherUrl(tokenModel.Token, timestamp), returnUrl) 55 }; 56 return; 57 } 58 LoginService service = new LoginService(); 59 var userinfo = service.GetUserInfo(ticket); 60 session[ConstantHelper.USER_SESSION_KEY] = userinfo; 61 //驗證通過,cache中去掉Token,保證每個token只能使用一次 62 cache.Remove(ConstantHelper.TOKEN_KEY); 63 } 64 } 65 66 /// <summary> 67 /// 生成跳轉腳本 68 /// </summary> 69 /// <param name="authernUrl">統一授權地址</param> 70 /// <param name="returnUrl">回調地址</param> 71 /// <returns></returns> 72 private string GetAuthernScript(string authernUrl, string returnUrl) 73 { 74 StringBuilder sbScript = new StringBuilder(); 75 sbScript.Append("<script type='text/javascript'>"); 76 sbScript.AppendFormat("window.location.href='{0}&returnUrl=' + encodeURIComponent('{1}');", authernUrl, returnUrl); 77 sbScript.Append("</script>"); 78 return sbScript.ToString(); 79 } 80 }
代碼說明:這里為了方便設置Token的過期時間,所以使用Cache來存取Token,設定Token的失效時間為兩分鍾,當驗證成功則從cache中移除Token。
調取過濾器
1 [Auth(Code = AuthCodeEnum.Login)] 2 public ActionResult Index() 3 { 4 return View(); 5 }
P站主要邏輯
P站收到授權請求,P站首先通過Coookie來判斷是否登陸,未登錄則跳轉至登陸頁面進行登陸操作。
1 /// <summary> 2 /// 授權登陸驗證 3 /// </summary> 4 /// <returns></returns> 5 [HttpPost] 6 public ActionResult PassportVertify() 7 { 8 var cookie=Request.Cookies[ConstantHelper.USER_COOKIE_KEY]; 9 if (cookie == null ||string.IsNullOrEmpty(cookie.ToString())) 10 { 11 return RedirectToAction("Login", new { ReturnUrl = Request["ReturnUrl"] ,Token= Request["Token"] }); 12 } 13 string userinfo = cookie.ToString(); 14 var success= passportservice.AuthernVertify(Request["Token"], Convert.ToDateTime(Request["TimeStamp"])); 15 if (!success) 16 { 17 return RedirectToAction("Login", new { ReturnUrl = Request["ReturnUrl"], Token = Request["Token"] }); 18 } 19 return Redirect(passportservice.GetReturnUrl(userinfo, Request["Token"],Request["ReturnUrl"])); 20 }
已登陸則驗證Token
1 /// <summary> 2 /// 驗證令牌 3 /// </summary> 4 /// <param name="token">令牌</param> 5 /// <param name="timestamp">時間戳</param> 6 /// <returns></returns> 7 public bool AuthernVertify(string token,DateTime timestamp) 8 { 9 return AuthernUtil.CreateToken(timestamp) == token; 10 }
測試說明
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、測試賬號和webconfig
<add key="PassportCenterUrl" value="http://www.passport.com:801"/>
用戶名:admin 密碼:123
demo
https://github.com/hexuefengx/study
