.net 單點登錄實踐


前言

  最近輪到我在小組晨會來分享知識點,突然想到單點登錄,准備來分享下如何實現單點登錄,所以有了下文。實現方案以及代碼可能寫得不是很嚴謹,有漏洞的地方或者錯誤的地方歡迎大家指正。  

  剛開始頭腦中沒有思路,直接在博客園里面看看別人是如何來實現的,看了幾篇文章發現,發現解決方案有點問題,或者說不算實現了單點登錄

名稱定義

  為了方便說明先說明幾個文中出現的名詞的含義:

  P站:統一登錄授權驗證中心,demo中 域名是www.passport.com:801

  A站:處於不同域名下的測試網站,demo中 域名是www.a.com:802

  B站:處於不同域名下的測試網站,demo中 域名是www.b.com:803

  Token:用戶訪問P站的秘鑰
  Ticket:用來保存用戶信息的加密字符串

單點登錄

  訪問A站需要登陸的就跳轉P站中進行登陸,P站登陸之后跳轉回至A站,用戶再次訪問B站需要登陸的頁面,用戶不需要進行登陸操作就可以正常訪問。

實現思路

  未登錄用戶訪問A站,首先會重定向跳轉至P站授權中心,P站首先通過檢測Cookie來判斷當前不是處於登陸狀態,就跳轉至登陸頁面進行登陸操作,登陸成功之后把用戶信息加密ticket附在A的請求地址上返回,A站通過解密ticket來獲取用戶信息,解密成功並存進Session中(這樣用戶在A中就處於登陸狀態了),訪問通過;當用戶再次訪問B站的時候,對於B站來說,用戶是處於未登錄狀態,則同樣會重定向跳轉至P站授權中心,P站檢測Cookie,判斷當前用戶處於登陸狀態,就把當前用戶信息加密成ticket附在B的請求地址上返回,后面的操作就和A站處理一樣;這樣都登陸之后再次訪問A或者B,A和B中Session中都存儲了用戶信息,就不會再次請求P站了。

簡單關系圖

泳道流程圖

主要邏輯說明

A站主要邏輯

  用戶首先訪問A站,A站中會生成Token,並存入Cache中。Token是A訪問P的鑰匙,P在回調給A的時候需要攜帶這個Token。A請求P,P驗證Token,P回調A,A檢測Token是否是發送出去的Token,驗證之后Token即失效,防止Token被再次使用。
  Token的生成是通過取時間戳的不同字段進行MD5加密生成,當然這里可以再加個鹽進行防偽。
 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

最后感謝大家的閱讀,這里推薦一個公眾號 猿大俠的客棧,會不定時的推送分享IT相關技術文章。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM