看這個方案之前,先說明下為什麼要加入SSO,以防對大家產生不好的影響。我們產品使用傳統winform+db服務+Db存儲方式開發,一群老菜幫子開發,以傳統的datatble做數據傳遞,很多年了未有變化。 然後我來了,感覺我這個老菜幫子都受不了這種開發,然後下定決心,作了一些封裝,看起來有點像orm的 ...
看這個方案之前,先說明下為什麼要加入SSO,以防對大家產生不好的影響。我們產品使用傳統winform+db服務+Db存儲方式開發,一群老菜幫子開發,以傳統的datatble做數據傳遞,很多年了未有變化。
然後我來了,感覺我這個老菜幫子都受不了這種開發,然後下定決心,作了一些封裝,看起來有點像orm的感覺了,並決定加入嵌入bs頁面,美化界面,並補充winform在圖表功能方面的短板。
然後就造成了各BS模塊分別嵌入到CS不同的頁面中,並且各BS模塊中有涉及到對業務的操作。很危險!因為在網頁中可以直接打開各BS模塊視圖,無需登錄,無需驗證等。針對這種情況,SSO需求由此而來…
目標:
1.各BS模塊統一使用單點登錄,不能沒有限制就使用業務系統
2.CS端嵌入BS時自動模擬SSO,實現可以免登錄使用頁面
下文SSO科普是複製不知道誰的博文的,具體是誰忘了,在此說明下
SSO整體流程圖:
SSO分為SSO-Server和SSO-Client兩個部分,SSO-Client可以是多個的,即各個需要單點登錄的client。
SSO-Server
SSO-Server主要負責用戶登錄、註銷、為SSO-Client分配token、驗證token的工作。
SSO-Server分配Token
為SSO-Client分配Token的部分,在SSO-Client請求SSO受信頁面的時候,檢查SSO-Server是否登錄,如果沒有登錄則跳轉到SSO-Server的登錄頁面,如果已登錄,則執行分配Token的代碼,在分配完成以後將TokenID作為參數添加到returnUrl中,並跳轉到returnUrl。
當完成Token分配之後,頁面將帶有Token的參數跳轉到SSO-Client頁面,併在SSO-Client的Cookie中添加Token值,在以後的每次請求中,SSO-Client通過調用SSO-Server的服務來驗證Token的合法性。
ValidateToken用來驗證TokenID的合法性,KeepToken用來保持Token不會過期。
SSO-Client通過調用Validate驗證Token,並得到當前的登錄用戶信息。
SSO-Client
SSO-Client作為受信系統來存在的,它自己沒有認證系統,只能通過SSO-Server來完成用戶身份認證的工作。
當用戶請求SSO-Client的受保護資源時,SSO-Client會首先是否有TokenID,如果存在TokenID,則調用SSO-Server的介面來驗證這個TokenID是否合法;
驗證成功以後將會返回SSOToken的實例,裡面包含已登錄的用戶信息
科普後動手:
MVC.SSO.Service:MVC+Redis,MVC實現SSO-Server的所有功能,存儲和時效使用Redis管理,並使用Redis共用Session
主要代碼:
/// <summary> /// 驗證是否包含該token,並返回信息 /// </summary> /// <param name="key"></param> /// <returns></returns> [HttpPost] public string ValidateToken(string key) { //var token = TokensManager.GetToken(key); try { MyToken token = new MyToken(); token.ValidateToken = false; if (BoolValidateToken(key)) { token.User = redis.StringGet<USERS>(key); token.ValidateToken = true; } return JsonConvert.SerializeObject(token); } catch (Exception ex) { throw new Exception("ValidateToken:" + ex.Message); } } /// <summary> /// 登錄 /// </summary> /// <param name="name"></param> /// <param name="passWord"></param> /// <param name="backUrl"></param> /// <returns></returns> [HttpPost] public string Login(string name, string passWord, string backUrl) { try { MyMsg msg = new MyMsg(); //tokenKey byte[] byts = System.Text.Encoding.Default.GetBytes(string.Format("{0},{1}", name, passWord)); var key = Convert.ToBase64String(byts); //判斷是否TokenIds是否已存在該用戶,不存在則判斷登錄,添加到token if (!BoolValidateToken(key)) { var user = userBll.GetUser(name, passWord); if (user == null) { msg.IsLogined = false; msg.Msg = "用戶名或密碼錯誤,登錄失敗!"; } msg.IsLogined = true; //並添加到全局變數中 //TokensManager.AddToken(key, user); Session["Token"] = key; redis.StringSet<USERS>(key, user); } Session["Token"] = key; Response.Cookies.Add(new HttpCookie("cToken", key)); if (!string.IsNullOrEmpty(backUrl)) { var url = backUrl + "?token=" + key; //Response.Redirect(backUrl + "?token=" + key, true);//生成一個tokenId 發放到客戶端 msg.BackUrl = url; } msg.Msg = "歡迎您:" + name; return JsonConvert.SerializeObject(msg); } catch (Exception ex) { throw; } }
BS端MVC-Client集成方案
MVC集成SSO方式為添加過濾器,併在需要添加SSO驗證的控制器上添加上該過濾器
主要代碼:
public override void OnActionExecuting(ActionExecutingContext filterContext) { base.OnActionExecuting(filterContext); try { bool isLogined = false;//登錄標誌 var cToken = filterContext.HttpContext.Request.Cookies["cToken"]; var token = filterContext.HttpContext.Request["token"]; var sessionKey = HttpContext.Current.Session["Token"]; if (token != null || sessionKey != null || cToken != null) {//eWFuc2hpLGE= token = token ?? sessionKey.ToString() ?? cToken.Value; var data = new { key = token }; //如果token不為空則去服務驗證token是否有效 HttpContent httpContent = new StringContent(JsonConvert.SerializeObject(data)); httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/json"); var httpClient = new HttpClient(); var responseJson = httpClient.PostAsync(SSOURL + "/ValidateToken", httpContent) .Result.Content.ReadAsStringAsync().Result; var myToken = JsonConvert.DeserializeObject<JH_OEMR_Model.MyToken>(responseJson); if (myToken.ValidateToken) { isLogined = true; } } if (!isLogined) filterContext.HttpContext.Response.Redirect(SSOURL+"?backUrl=" + filterContext.HttpContext.Request.Url, true); } catch (Exception ex) { filterContext.HttpContext.Response.Write("aaaaa"+ex.Message.ToString()); throw; } }
CS端集成SSO:
CS端載入BS頁面時,先判斷是否模擬登錄,如未登錄,模擬登錄,然後訪問BS端網址帶上token即可
主要代碼:
private static void LoginSSO() { var data = new { name = UName, passWord = UPwd, backUrl = string.Empty }; HttpContent httpContent = new StringContent(JsonConvert.SerializeObject(data)); httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/json"); var httpClient = new HttpClient(); var responseJson = httpClient.PostAsync(SSOURL+"/Login", httpContent) .Result.Content.ReadAsStringAsync().Result; var myToken = JsonConvert.DeserializeObject<MyMsg>(responseJson); if (myToken.IsLogined) { IsLoginedSSO = true; } }