MVC - Single Sign-on Middleware

Keywords: ASP.NET Session Redis SDK Database

This chapter will share with you a single sign-on middleware. The middleware sounds profound. In fact, this middleware just encapsulates the logic and processing flow used in single sign-on into several methods. The default support uses redis service to save sessions. It can also use parameter Func<> method to customize session storage operation. It does not need red provided by my default. Is storage method; to say the source of the content of this chapter, actually I was in the past. ShenNiu.MVC This questionnaire is not a website, but my questionnaire system can definitely need the information of the logger when maintaining the questionnaire or the topic. I don't want to work out a set of account procedures separately, so I use this single sign-on mode to provide the required questionnaire. User information, as well as the need to manage user information for a module written by oneself in the near future, can omit the user module, and have to say that single sign-on plays a great role at this moment. I hope you like the content of this chapter, and I hope you have more "scanner support" and "recommendation" thank you! If you want to share more information about mvc with us Ninesky Framework Author: The official group 428310563 designated by Dongting Xizhao exchanges;

 

Hand-drawn illustration of single sign-on authentication

ShenNiuApi.SDK Packaging Middleware Code

Examples of Middleware Used in Questionnaire System

Promotion of Questionnaire System

 

The next step is to share one footprint:

Hand-drawn illustration of single sign-on authentication

First of all, we need to understand the process of execution and the principle of operation in order to make a simple single sign-on function. Here we focus on what I think is the key point. First, we put forward a manual map:

It seems that the picture is not very beautiful, but the meaning I want to express is clearly expressed. As a single sign-on authentication module, the main process is as follows:

1. When not logged in: Provide a unified login entry ="to verify the correctness of the account in the database =" Store session (here using redis to store token and user login information, using its data expiration strategy as session session session mechanism)="Redirect to the address specified by redirectUrl

2. When logged in: Get the session Id (token) = of cookie storage in the site, call validation token valid interface= There are two cases (a,b)

(a) Valid token="Gets the session stored information of the logged-in user (value information stored by redis)

b) Invalid token="Returns invalid information and constructs login entry address

Through the above analysis, the general process should be very clear. Let's look at the encapsulated code below.

 

ShenNiuApi.SDK Packaging Middleware Code

Here are three methods of middleware: SsoMiddleWareServer, SsoMiddleWareClient, SsoMiddleWareLoginOut. Here I have packaged the method into nuget: Install-Package ShenNiuApi.SDK. Just download the latest sdk, you can easily achieve a single-point login. Recording architecture, the next look at the specific code;

SsoMiddleWareServer (login entry operation):

 1         /// <summary>
 2         /// Single sign-on operation SSOMiddleWare Server side(Method function:
 3         /// 1.generate sessionId 
 4         /// 2.storage session reach redis(60 Minute failure)Or customize sessionStoreFunc Method medium 
 5         /// 3.Structural bearing token Redirectional address)
 6         /// Note: Default redis Preservation session,So we need to conf Middle configuration ReadAndWritePorts and OnlyReadPorts Two appSettings Node:
 7         /// ReadAndWritePorts stay conf Configuration formats such as: pwd@ip:port,Multiple uses'|'Separate       Example: shenniubuxing3@127.0.0.1:6377
 8         /// OnlyReadPorts stay conf Configuration formats such as: pwd@ip:port,Multiple uses'|'Separate              Example: shenniubuxing3@127.0.0.1:6377
 9         /// </summary>
10         /// <typeparam name="TUserBaseInfo">Objects that store login information</typeparam>
11         /// <param name="userBaseInfo">login information</param>
12         /// <param name="redirectUrl">Redirect address(Note: The format should be http://Or https://; address transcoded by UrlEncode; no http://tag is required if it is below the same site</param>
13         /// <param name="token">Execution method is correct ref Return unique token(Note: token Generation rules are unique tokenKey+guid+time stamp)</param>
14         /// <param name="tokenKey">generate token Of Key(Default: 66666666)</param>
15         /// <param name="sessionStoreFun">custom session Storage Method (Providing Custom Operation Save session Override the default reids Storage mode)</param>
16         /// <param name="timeOut">60(Minutes)</param>
17         /// <returns>Supplemental token Redirectional address</returns>
18         public string SsoMiddleWareServer<TUserBaseInfo>(TUserBaseInfo userBaseInfo, string redirectUrl, ref string token, string tokenKey = "666666", Func<TUserBaseInfo, bool> sessionStoreFun = null, int timeOut = 60)
19             where TUserBaseInfo : class,new()
20         {
21             var returnUrl = string.Empty;
22             try
23             {
24                 //Non empty verification  
25                 if (string.IsNullOrWhiteSpace(redirectUrl) || userBaseInfo == null) { return returnUrl; }
26 
27                 //generate Token
28                 token = Md5Extend.GetSidMd5Hash(tokenKey);
29 
30                 // ShenNiuApi Default Redis storage session
31                 if (sessionStoreFun == null && userBaseInfo != null)
32                 {
33                     if (!CacheRepository.Current(CacheType.RedisCache).SetCache<TUserBaseInfo>(token, userBaseInfo, timeOut, true)) { return returnUrl; }
34                 }
35                 else { if (!sessionStoreFun(userBaseInfo)) { return returnUrl; } }
36 
37                 //Domain name system login
38                 if (!Uri.IsWellFormedUriString(redirectUrl, UriKind.Absolute))
39                 {
40                     returnUrl = redirectUrl;
41                     return returnUrl;
42                 }
43 
44                 #region Parse and construct jump links
45                 redirectUrl = HttpUtility.UrlDecode(redirectUrl);
46                 redirectUrl = redirectUrl.TrimEnd('&');
47                 redirectUrl = Regex.Replace(redirectUrl, "(&)?token=[^&]+(&)?", "");
48                 Uri uri = new Uri(redirectUrl);
49                 var queryStr = uri.Query;
50                 redirectUrl += queryStr.Contains('?') ? "" : "?";
51                 redirectUrl += string.IsNullOrWhiteSpace(queryStr.TrimStart('?')) ? "" : "&";
52                 returnUrl = string.Format("{0}token={1}", redirectUrl, token);
53                 #endregion
54             }
55             catch (Exception ex)
56             {
57                 throw new Exception(ex.Message);
58             }
59             finally
60             {
61                 if (string.IsNullOrWhiteSpace(returnUrl)) { token = string.Empty; }
62             }
63             return returnUrl;
64         }

SsoMiddleWareClient (Token authentication and access to login information):

 1   /// <summary>
 2         /// Single sign-on operation SSOMiddleWare Client(Method function:
 3         /// 1.Verify whether the client has sid perhaps url Address with the latest token 
 4         /// 2.Access Server session Basic information (Note: Read directly from the server by default) redis Library, same server The method also needs to configure the corresponding account node. ReadAndWritePorts and OnlyReadPorts)
 5         /// 3.Reset Client cookie Validity Period and Server Storage session Validity period)
 6         /// </summary>
 7         /// <typeparam name="TUserBaseInfo">Login User Information Object</typeparam>
 8         /// <param name="httpContext">context HttpContext</param>
 9         /// <param name="ssoLoginUrl">sso Unified landing entry address</param>
10         /// <param name="redirectUrl">Address to be redirected</param>
11         /// <param name="userBaseInfo">Accessed login user information</param>
12         /// <param name="token">Only token(Namely: sid)</param>
13         /// <param name="getOrsetSessionFun">Customize access to server-side user information methods and at the same time meet the need to reset the new session Effective time</param>
14         /// <param name="sidName">cookie Preserved sid Name</param>
15         /// <param name="timeOut">Expiration date</param>
16         /// <returns></returns>
17         public string SsoMiddleWareClient<TUserBaseInfo>(HttpContext httpContext, string ssoLoginUrl, string redirectUrl, ref TUserBaseInfo userBaseInfo, ref string token, Func<string, int, TUserBaseInfo> getAndsetSessionFun = null, string sidName = "sid", int timeOut = 60)
18                where TUserBaseInfo : class,new()
19         {
20             var returnUrl = string.Empty;
21             try
22             {
23                 userBaseInfo = default(TUserBaseInfo);
24                 token = string.Empty;
25                 if (string.IsNullOrWhiteSpace(ssoLoginUrl) || string.IsNullOrWhiteSpace(redirectUrl) || string.IsNullOrWhiteSpace(sidName)) { return returnUrl; }
26 
27                 //Post-expiration verification of settings url strand 
28                 returnUrl = string.Format("{0}?returnUrl={1}", ssoLoginUrl, HttpUtility.UrlEncode(redirectUrl));
29 
30                 //Obtain token
31                 var cookie = httpContext.Request.Cookies.Get(sidName);
32                 token = httpContext.Request.Params["token"];
33                 token = string.IsNullOrWhiteSpace(token) ? (cookie == null ? "" : cookie.Value) : token;
34                 if (string.IsNullOrWhiteSpace(token)) { return returnUrl; }
35 
36                 //Getting basic user information
37                 if (getAndsetSessionFun != null)
38                 {
39                     userBaseInfo = getAndsetSessionFun(token, timeOut);
40                 }
41                 else
42                 {
43                     userBaseInfo = CacheRepository.Current(CacheType.RedisCache).GetCache<TUserBaseInfo>(token, true);
44                 }
45                 if (userBaseInfo == null)
46                 {
47                     //Be overdue cookie,empty
48                     if (cookie != null)
49                     {
50                         cookie.Expires = DateTime.Now.AddDays(-1);
51                         httpContext.Response.SetCookie(cookie);
52                     }
53                     return returnUrl;
54                 }
55 
56                 //cookie Cleared, need to reset
57                 if (cookie == null)
58                 {
59                     cookie = new HttpCookie(sidName, token);
60                     cookie.Expires = DateTime.Now.AddMinutes(timeOut);
61                     httpContext.Response.AppendCookie(cookie);
62                 }
63                 else
64                 {
65                     //After successful login validation, you need to reset cookie Medium toke Failure time
66                     cookie.Value = token;
67                     cookie.Expires = DateTime.Now.AddMinutes(timeOut);
68                     httpContext.Response.SetCookie(cookie);
69                 }
70 
71                 //Setting up the server session Failure time
72                 if (getAndsetSessionFun == null)
73                 {
74                     CacheRepository.Current(CacheType.RedisCache).AddExpire(token, timeOut);
75                 }
76                 returnUrl = string.Empty;
77             }
78             catch (Exception ex)
79             {
80                 throw new Exception(ex.Message);
81             }
82             finally { if (!string.IsNullOrWhiteSpace(returnUrl)) { token = string.Empty; } }
83             return returnUrl;
84         }

SsoMiddleWareLoginOut (logout operation):

 1  /// <summary>
 2         /// Single sign-on operation SSOMiddleWare Quit landing
 3         /// </summary>
 4         /// <param name="httpContext">Http To the following</param>
 5         /// <param name="removeSession">Custom Removal Method</param>
 6         /// <param name="sidName">cookie Preserved sid Name</param>
 7         /// <returns>true or false</returns>
 8         public bool SsoMiddleWareLoginOut(HttpContext httpContext, Func<string, bool> removeSession = null, string sidName = "sid")
 9         {
10             var isfalse = true;
11             try
12             {
13                 if (string.IsNullOrWhiteSpace(sidName)) { sidName = "sid"; }
14 
15                 //Obtain cookie Medium token
16                 var cookie = httpContext.Request.Cookies.Get(sidName);
17                 if (cookie == null) { return isfalse; }
18 
19                 //Set expiration date cookie(Expire first cookie)
20                 var key = cookie.Value;
21                 cookie.Expires = DateTime.Now.AddDays(-1);
22                 httpContext.Response.SetCookie(cookie);
23 
24                 //remove session
25                 if (removeSession != null)
26                 {
27                     isfalse = removeSession(key);
28                 }
29                 else
30                 {
31                     isfalse = CacheRepository.Current(CacheType.RedisCache).Remove(key);
32                 }
33             }
34             catch (Exception ex)
35             {
36 
37                 throw new Exception(ex.Message);
38             }
39             return isfalse;
40         }

The parameters and functions of each method are annotated in every line of logical code, so you may read it. What we want to say here is that every method has the steps of operating redis storage session by default, so we can see that redis service storage session is used by default in this middleware.

Someone will ask why you do this. Doesn't the bottom layer of your single sign-on use interfaces to operate login or authentication? Consider a practical scenario where, as an employee of a small and medium-sized company, access to a server usually deploys the entire company's site, such as: Site 1, Site 2... Although the domain name is different, they are all on the same server. Imagine if redis is used to store session sessions, can you think that my server has direct access to redis now? If redis service is also on this server, of course, let alone redis service, then I directly embedded public operation redis in the middleware to get session, store session and other operations is not a problem, so we still need to have a session (token) authentication api, no necessary things (for single sign-on sites and redirection sites) Points are not necessary), so I did that by embedding a default redis operation haha (which can be discerned); although more business scenarios have to be considered, in case login bills and other sites are not on one server (or can't access redis directly), here is a Func <> parameter in three middleware method parameters, each method's Fu parameter. NC <> means a little different, you can see the comment; with this custom Func, middleware can recognize that if the client has passed this method, then use Func as the main method, instead of using the default way to operate redis, which allows the user to customize the method to expand the API or other reasonable way that the user thinks to call token authentication, which also guarantees the party. The universality of the method.

 

Examples of Middleware Used in Questionnaire System

Next I'll use real examples to use ShenNiuApi.SDK The middleware method in this paper is an example of how to use it in my questionnaire system. First, download the latest sdk of Install-Package ShenNiuApi.SDK through nuget, and then add the following code in the Filter for login validation or in the parent class inheriting Controller (I am the latter here):

 1 public class BaseController : Controller
 2     {
 3 
 4         protected StageModel.MoUserData _userData;
 5 
 6         protected override void OnActionExecuting(ActionExecutingContext filterContext)
 7         {
 8 
 9             #region Use ShenNiuApiClient Of SsoClient middleware
10 
11             ShenNiuApi.SDK.ShenNiuApiClient client = new ShenNiuApi.SDK.ShenNiuApiClient();
12 
13             var ssoLogin="http://www.lovexins.com:8081/User/Login";
14             var redirectUrl = filterContext.HttpContext.Request.Url.AbsoluteUri;
15             var token = string.Empty;
16             var returnUrl = client.SsoMiddleWareClient<StageModel.MoUserData>(System.Web.HttpContext.Current, ssoLogin, redirectUrl, ref this._userData, ref token);
17             if (string.IsNullOrWhiteSpace(token) )
18             {
19                 filterContext.Result = new RedirectResult(returnUrl);
20                 return;
21             }
22             #endregion
23         }
24 
25         protected void ShowMsg(string msg)
26         {
27 
28             ModelState.AddModelError(string.Empty, msg);
29         }
30     }

Only one sentence is needed for client.SsoMiddleWareClient <StageModel.MoUserData> (System.Web.HttpContext.Current, ssoLogin, redirectUrl, Ref. _userData, ref token); it can verify the single sign-on of the questionnaire system and obtain the information of the logged-in user, and all kinds of cookie information for parsing and setting sid have been completed in the middleware method, which greatly reduces your editing. In order to compare the following, I directly post the amount of code without using the SsoMiddleWareClient method:

 1 protected override void OnActionExecuting(ActionExecutingContext filterContext)
 2         {
 3 
 4 
 5             var returnUrl = filterContext.HttpContext.Request.Url.AbsoluteUri;
 6             returnUrl = HttpUtility.UrlEncode(returnUrl);
 7             // var result = new RedirectResult(string.Format("http://www.lovexins.com:8081/User/Login?returnUrl={0}", returnUrl));
 8             var result = new RedirectResult(string.Format("http://172.16.9.6:4040/User/Login?returnUrl={0}", returnUrl));
 9             var key = "Sid";
10             var timeOut = 30;
11             try
12             {
13                 var cookie = filterContext.HttpContext.Request.Cookies.Get(key);
14                 var token = filterContext.HttpContext.Request.Params["token"];
15                 token = string.IsNullOrWhiteSpace(token) ? (cookie == null ? "" : cookie.Value) : token;
16                 if (string.IsNullOrWhiteSpace(token))
17                 {
18                     filterContext.Result = result;
19                     return;
20                 }
21 
22                 this._userData = CacheRepository.Current(CacheType.RedisCache).GetCache<StageModel.MoUserData>(token, true);
23                 if (this._userData == null && cookie != null)
24                 {
25                     //empty cookie
26                     cookie.Expires = DateTime.Now.AddDays(-1);
27                     filterContext.HttpContext.Response.SetCookie(cookie);
28                     filterContext.Result = result;
29                     return;
30                 }
31                 else if (this._userData == null)
32                 {
33                     filterContext.Result = result;
34                     return;
35                 }
36 
37                 if (cookie == null)
38                 {
39                     cookie = new HttpCookie(key, token);
40                     cookie.Expires = DateTime.Now.AddMinutes(timeOut);
41                     filterContext.HttpContext.Response.AppendCookie(cookie);
42                 }
43                 else
44                 {
45                     cookie.Value = token;
46                     //After successful login validation, you need to reset cookie Medium toke Failure time
47                     cookie.Expires = DateTime.Now.AddMinutes(timeOut);
48                     filterContext.HttpContext.Response.SetCookie(cookie);
49                 }
50 
51                 //Set up session Failure time
52                 CacheRepository.Current(CacheType.RedisCache).AddExpire(token, timeOut);
53             }
54             catch (Exception ex)
55             {
56                 filterContext.Result = result;
57                 return;
58             }
59         }

The former is much simpler from the point of view of code volume. Some people will say that you just got a method, and that the amount of code is less than Ha-ha. This has to say that we usually use third-party plug-ins or class libraries, which greatly reduces our workload and improves the development speed. ShenNiuApi.SDK What else do you need to worry about? But when you look at the concrete steps in it, I yell in favor of the logical code.

With the customized Controller parent class in the questionnaire, we also need to have a place to log in. Here, Stage.Web, my newly created project, adds the following code to the Action of its login get request:

 1    #region Use ShenNiuApiClient Of SsoClient middleware
 2 
 3             ShenNiuApi.SDK.ShenNiuApiClient client = new ShenNiuApi.SDK.ShenNiuApiClient();
 4             var ssoLogin = loginUrl;
 5             var redirectUrl = context.Request.Path;
 6 
 7             var token = string.Empty;
 8             t = default(T);
 9             var returnUrl = client.SsoMiddleWareClient<T>(System.Web.HttpContext.Current, ssoLogin, redirectUrl, ref t, ref token, sidName: UserLoginExtend.CookieName);
10             if (string.IsNullOrWhiteSpace(token))
11             {
12                 return new RedirectResult(returnUrl);
13             }
14             return null;
15             #endregion

Get the token of the login directly through the SsoMiddleWareClient method provided by the middleware and verify whether it has been logged in, if it has logged in directly through the return new Redirect Result (return Url); redirect to the address of the return Url; if not, enter the login interface and enter the account information after:

Submit login, enter Action of our post s, enter database to match account successfully, then directly call middleware method to store session and provide unique token value, then redirect jump:

 1  #region Use ShenNiuApiClient Of SsoServer middleware
 2 
 3                     ShenNiuApi.SDK.ShenNiuApiClient client = new ShenNiuApi.SDK.ShenNiuApiClient();
 4 
 5                     var timeOut = 60; //Minute
 6                     var token = string.Empty;
 7                     var redirectUrl = client.SsoMiddleWareServer<StageModel.MoUserData>(userData, returnUrl, ref token, timeOut: timeOut);
 8                     sbLog.AppendFormat("redirectUrl:{0},token:{1},", redirectUrl, token);
 9                     if (string.IsNullOrWhiteSpace(token) || string.IsNullOrWhiteSpace(redirectUrl))
10                     {
11                         //Landing failed
12                         sbLog.Append("Landing failed,");
13                     }
14                     else
15                     {
16                         //Write in Sso Unified landing site sid reach cookie
17                         var cookie = new HttpCookie(UserLoginExtend.CookieName, token);
18                         cookie.Expires = DateTime.Now.AddMinutes(timeOut);
19                         cookie.Domain = Request.Url.Host;
20                         HttpContext.Response.AppendCookie(cookie);
21                     }
22                     var isAddLog = await StageClass._WrigLogAsync(sbLog.ToString());
23                     return new RedirectResult(string.Format("{0}", redirectUrl));
24                     #endregion

So far, the code of sso is basically complete, but the default is that I embedded redis service to store session information, so we need to configure a redis-related account password and other nodes, here you only need to add the following name xml file under the C:\ Conf ShenNiuApi. xml disk, the content of the file is simple:

 1 <ShenNiuApi>
 2     <RedisCache>
 3 <! - Read and write permission service addresses separated by'|'(format such as: pwd@ip:port) -->.
 4         <UserName>shenniubuxing3@111.111.111.152:1111</UserName>
 5 <! - Read-only permission service address, multiple use'|'separated - >
 6         <UserPwd>shenniubuxing3@111.111.111.152:1111|shenniubuxing3@127.0.0.1:6377</UserPwd>
 7         <ApiUrl></ApiUrl>
 8         <ApiKey></ApiKey>
 9     </RedisCache>
10 </ShenNiuApi>

Change the redis account, password, port and address in the content to your own; because it's on the C disk that other sites on your server can access. If you use redis to store session s by default, you can quickly build a single sign-on architecture by following the above steps. Here I provide the place where the following questionnaire uses the single sign-on test. Address: www.lovexins.com:1001/Subject Test account: shenniu003 password: 123123. Note that the domain name of the login interface is the same as the domain name of the questionnaire survey, but the port is different. If you want to see the effect, you can use F12 in the browser, and then operate as shown in the figure.

You can see that this sid is the token value in the address bar. This is the session Id pull defined by them. Wouldn't you like to try it?

 

Promotion of Questionnaire System

Questionnaire I think many companies will use, you will generally do their own set, I would like to recommend here is for you. Shen bull questionnaire How to try it out? You can login to the address. http://www.lovexins.com:8081/User/Login Account: shenniiu003 password: 123123, after entering the system, click "Questionnaire Management"=> "Questionnaire". Here you can add the information and options of the questionnaire you want to investigate:

If you add the completed questionnaire information, you can click "Read" to see the content and way of your questionnaire display (support mobile phone browsing access). This is also the interface that the people who fill in the questionnaire see. At present, the types of topics supported are (single-choice, multiple-choice, text input), test address: http://www.lovexins.com:1001/shenniu003/wenjuan7 In the address shenniu003 It is displayed according to the account number. If you are the hr or the owner of a company, the address bar can be registered directly as your company's phonetic name or Chinese characters (does it feel OK): _________

The key point has come. With the users who fill in the form, they need to analyze and make statistics. At this time, you just need to click on the "statistics" in the questionnaire list to see the chart of the following names:

You can click on the "red" bar corresponding to a question option and go directly to the user option analysis report:

It seems that the effect is quite good. The key point is that when the data statistics are shown to the boss or other friends, it makes people feel "tall". This is the statistical chart of the option style. Then if the user fills in the class statistics, it is the following list:

Characteristic:

1. Topic types with rich choices, multiple choices and user-filled categories

2. Single Sign-on Architecture, which can be quickly embedded into other systems

3. Mobile web can also access questionnaires and answer questions.

4. Detailed Statement Statistics

5. Professional maintenance personnel Haha

Explanation: The last thing to say is that this questionnaire system is for the convenience of friends and enterprises who need to use this function. If you think you can send one or two questionnaires, you can contact me and let me assign you a separate manager account. Of course, if you are the leader of an enterprise and want to use this survey system for a long time, you can contact the mailbox: 841202396@q.com, whatever you like. How many questionnaires do you send as long as they meet the legal requirements?

Posted by keystroke on Wed, 03 Apr 2019 17:45:31 -0700