A few days ago, a friend asked me to help with a single sign-on. In fact, this concept has long been familiar, but there are few practical applications. I have been free recently, so I decided to describe an SSO solution in detail through this article. I hope it will be helpful to everyone. helped. There are many SSO solutions, but the search results are disappointing. Most of them are reposted from each other, and the descriptions are superficial.
Without further ado, let’s get to the point. My idea is to use centralized verification and centralized Passport verification on multiple sites. As shown in the figure below:
#In order to facilitate a clear description, we first define a few nouns. Everything that appears in this article has the following meanings.
Main site: Passport centralized verification server http://www.passport.com/.
Branch: http://www.a.com/, http://www.b.com/, http://www.c.com/
Credential: Data identification generated after the user logs in , used to identify authorized users, can be used in various ways. In the DEMO, I use Cache for the main site and Session for the sub-sites.
Token: A unique identifier issued by Passport that can be circulated in each branch.
OK, now describe the single sign-on process:
Scenario 1. Anonymous user: An anonymous user accesses an authorization page on branch a. First, it jumps to the main site and allows the user to enter their account number and password to log in. After passing the verification, the main site credentials are generated, and the token is generated at the same time, and jumps back to sub-site a. At this time, sub-station a detects that the user already holds the token, so it uses the token to go to the main site again to obtain the user credentials. After successful acquisition, it is allowed The user accesses the authorization page. At the same time, the local credentials of branch a are generated. When the user needs to be authenticated again, the local credentials will be checked first to reduce network interaction.
Scenario 2: A user logged in at sub-site a visits sub-site b: Because the user has logged in at sub-site a and already holds a token, sub-site b will use the token to go to the main site to obtain the user credentials, and the acquisition is successful. Then allow the user to access the authorization page. At the same time, the local credentials of substation b are generated.
After the design is completed, here are some key points for the implementation of the solution:
Token: The token is issued by the main station, and the main station issues the token and generates users at the same time Credentials, and record the correspondence between tokens and user credentials to respond to the corresponding credentials based on the token provided by the user; tokens need to be circulated in various cross-domain sub-stations, so I use the main station’s token in the DEMO Cookie and specify Cookie.Domain="passport.com". How do each branch site share the cookies of the main site? Redirect from the sub-site to the main site page, and then the page reads the cookie and sends it back in the form of URL parameters. You can check the detailed implementation in the DEMO code. Of course, if anyone has a better token implementation, please share it. .
//产生令牌 string tokenValue = Guid.NewGuid().ToString().ToUpper(); HttpCookie tokenCookie = new HttpCookie("Token"); tokenCookie.Values.Add("Value", tokenValue); tokenCookie.Domain = "passport.com"; Response.AppendCookie(tokenCookie);
Main site credential: The main site credential is a relational table that contains three fields: token, credential data, and expiration time. There are many implementation methods to choose from. If reliability is required, use the database. If performance is required, use Cache. In the DEMO, I used the DataTable in Cache. As shown in the following code:
/// <summary> /// 初始化数据结构 /// </summary> /// <remarks> /// ---------------------------------------------------- /// | token(令牌) | info(用户凭证) | timeout(过期时间) | /// |--------------------------------------------------| /// </remarks> private static void cacheInit() { if (HttpContext.Current.Cache["CERT"] == null) { DataTable dt = new DataTable(); dt.Columns.Add("token", Type.GetType("System.String")); dt.Columns["token"].Unique = true; dt.Columns.Add("info", Type.GetType("System.Object")); dt.Columns["info"].DefaultValue = null; dt.Columns.Add("timeout", Type.GetType("System.DateTime")); dt.Columns["timeout"].DefaultValue = DateTime.Now.AddMinutes(double.Parse(System.Configuration.ConfigurationManager.AppSettings["timeout"])); DataColumn[] keys = new DataColumn[1]; keys[0] = dt.Columns["token"]; dt.PrimaryKey = keys; //Cache的过期时间为 令牌过期时间*2 HttpContext.Current.Cache.Insert("CERT", dt, null, DateTime.MaxValue, TimeSpan.FromMinutes(double.Parse(System.Configuration.ConfigurationManager.AppSettings["timeout"]) * 2)); } }
Sub-site credentials: Sub-site credentials are mainly used to reduce network interaction during repeated verification. For example, the user has logged in to sub-site a. When he visits sub-site a again, There is no need to use the token to go to the main site for verification, because branch a already has the user's credentials. The sub-site credentials are relatively simple and can use Session or Cookie.
Base class of sub-site SSO page: The page using SSO in the sub-site will perform a series of logical judgment processing, such as the flow chart at the beginning of the article. If there are multiple pages, it is impossible to write such logic for each page. OK, then encapsulate this set of logic into a base class, and all pages that want to use SSO can inherit this base class. The following code is shown:
using System; using System.Data; using System.Configuration; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; using System.Text.RegularExpressions; namespace SSO.SiteA.Class { /// <summary> /// 授权页面基类 /// </summary> public class AuthBase : System.Web.UI.Page { protected override void OnLoad(EventArgs e) { if (Session["Token"] != null) { //分站凭证存在 Response.Write("恭喜,分站凭证存在,您被授权访问该页面!"); } else { //令牌验证结果 if (Request.QueryString["Token"] != null) { if (Request.QueryString["Token"] != "$Token$") { //持有令牌 string tokenValue = Request.QueryString["Token"]; //调用WebService获取主站凭证 SSO.SiteA.RefPassport.TokenService tokenService = new SSO.SiteA.RefPassport.TokenService(); object o = tokenService.TokenGetCredence(tokenValue); if (o != null) { //令牌正确 Session["Token"] = o; Response.Write("恭喜,令牌存在,您被授权访问该页面!"); } else { //令牌错误 Response.Redirect(this.replaceToken()); } } else { //未持有令牌 Response.Redirect(this.replaceToken()); } } //未进行令牌验证,去主站验证 else { Response.Redirect(this.getTokenURL()); } } base.OnLoad(e); } /// <summary> /// 获取带令牌请求的URL /// 在当前URL中附加上令牌请求参数 /// </summary> /// <returns></returns> private string getTokenURL() { string url = Request.Url.AbsoluteUri; Regex reg = new Regex(@"^.*\?.+=.+$"); if (reg.IsMatch(url)) url += "&Token=$Token$"; else url += "?Token=$Token$"; return "http://www.passport.com/gettoken.aspx?BackURL=" + Server.UrlEncode(url); } /// <summary> /// 去掉URL中的令牌 /// 在当前URL中去掉令牌参数 /// </summary> /// <returns></returns> private string replaceToken() { string url = Request.Url.AbsoluteUri; url = Regex.Replace(url, @"(\?|&)Token=.*", "", RegexOptions.IgnoreCase); return "http://www.passport.com/userlogin.aspx?BackURL=" + Server.UrlEncode(url); } }//end class }
User exit: When the user exits, the main station credentials and the current sub-station credentials are cleared respectively. If site A is required to exit, and sites B and C also exit, you can expand the interface to clear the credentials of each substation.
Clear the expired credentials/tokens of the main site: regularly clear the records whose timeout field in (DataTable) Cache["CERT"] exceeds the current time.
For more articles related to .Net-based single sign-on (SSO) implementation solutions, please pay attention to the PHP Chinese website!