前言
最近輪到我在小組晨會來分享知識點,突然想到單點登錄,準(zhǔn)備來分享下如何實現(xiàn)單點登錄,所以有了下文。實現(xiàn)方案以及代碼可能寫得不是很嚴(yán)謹(jǐn),有漏洞的地方或者錯誤的地方歡迎大家指正。
剛開始頭腦中沒有思路,直接在博客園里面看看別人是如何來實現(xiàn)的,看了幾篇文章發(fā)現(xiàn),發(fā)現(xiàn)解決方案有點問題,或者說不算實現(xiàn)了單點登錄
名稱定義
為了方便說明先說明幾個文中出現(xiàn)的名詞的含義:
P站:統(tǒng)一登錄授權(quán)驗證中心,demo中 域名是www.passport.com:801
A站:處于不同域名下的測試網(wǎng)站,demo中 域名是www.a.com:802
B站:處于不同域名下的測試網(wǎng)站,demo中 域名是www.b.com:803
Token:用戶訪問P站的秘鑰
Ticket:用來保存用戶信息的加密字符串
單點登錄
訪問A站需要登陸的就跳轉(zhuǎn)P站中進(jìn)行登陸,P站登陸之后跳轉(zhuǎn)回至A站,用戶再次訪問B站需要登陸的頁面,用戶不需要進(jìn)行登陸操作就可以正常訪問。
實現(xiàn)思路
未登錄用戶訪問A站,首先會重定向跳轉(zhuǎn)至P站授權(quán)中心,P站首先通過檢測Cookie來判斷當(dāng)前不是處于登陸狀態(tài),就跳轉(zhuǎn)至登陸頁面進(jìn)行登陸操作,登陸成功之后把用戶信息加密ticket附在A的請求地址上返回,A站通過解密ticket來獲取用戶信息,解密成功并存進(jìn)Session中(這樣用戶在A中就處于登陸狀態(tài)了),訪問通過;當(dāng)用戶再次訪問B站的時候,對于B站來說,用戶是處于未登錄狀態(tài),則同樣會重定向跳轉(zhuǎn)至P站授權(quán)中心,P站檢測Cookie,判斷當(dāng)前用戶處于登陸狀態(tài),就把當(dāng)前用戶信息加密成ticket附在B的請求地址上返回,后面的操作就和A站處理一樣;這樣都登陸之后再次訪問A或者B,A和B中Session中都存儲了用戶信息,就不會再次請求P站了。
簡單關(guān)系圖
泳道流程圖
主要邏輯說明
A站主要邏輯
用戶首先訪問A站,A站中會生成Token,并存入Cache中。Token是A訪問P的鑰匙,P在回調(diào)給A的時候需要攜帶這個Token。A請求P,P驗證Token,P回調(diào)A,A檢測Token是否是發(fā)送出去的Token,驗證之后Token即失效,防止Token被再次使用。
Token的生成是通過取時間戳的不同字段進(jìn)行MD5加密生成,當(dāng)然這里可以再加個鹽進(jìn)行防偽。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
/// <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的時候進(jìn)行,A中對Token進(jìn)行校驗,校驗不成功則請求P站統(tǒng)一授權(quán)驗證。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
|
/// <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> /// 驗證權(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驗證不通過或者沒有取到從P回調(diào)的ticket 都進(jìn)行再次請求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è)計過期時間為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; //驗證通過,cache中去掉Token,保證每個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的過期時間,所以使用Cache來存取Token,設(shè)定Token的失效時間為兩分鐘,當(dāng)驗證成功則從cache中移除Token。
調(diào)取過濾器
1
2
3
4
5
|
[Auth(Code = AuthCodeEnum.Login)] public ActionResult Index() { return View(); } |
P站主要邏輯
P站收到授權(quán)請求,P站首先通過Coookie來判斷是否登陸,未登錄則跳轉(zhuǎn)至登陸頁面進(jìn)行登陸操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
/// <summary> /// 授權(quá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" ])); } |
已登陸則驗證Token
1
2
3
4
5
6
7
8
9
10
|
/// <summary> /// 驗證令牌 /// </summary> /// <param name="token">令牌</param> /// <param name="timestamp">時間戳</param> /// <returns></returns> public bool AuthernVertify( string token,DateTime timestamp) { return AuthernUtil.CreateToken(timestamp) == token; } |
測試說明
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
下載地址:源碼下載地址
原文鏈接:http://www.cnblogs.com/minesnil-forfaith/p/6062943.html
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持服務(wù)器之家。