Asp.net core certification and simple cluster, asp. netcore

Source: Internet
Author: User

Asp.net core certification and simple cluster, asp. netcore

As we all know, in Asp.net WebAPI, authentication is implemented through the AuthenticationFilter filter. Our common practice is to customize the AuthenticationFilter to implement authentication logic, pass authentication, continue pipeline processing, and authentication fails, the authentication failure result is directly returned, similar to the following:

Public async Task AuthenticateAsync (HttpAuthenticationContext context, CancellationToken cancellationToken) {var principal = await this. authenticateAsync (context. request); if (principal = null) {context. request. headers. getCookies (). clear (); context. errorResult = new AuthenticationFailureResult ("Unauthorized request", context. request);} else {context. principal = principal ;}}

However, in. net core, AuthenticationFilter no longer exists and is replaced by the authentication middleware. As for the reason, I think Microsoft thinks that Authentication is not closely related to the business, and it is more appropriate to put it in pipeline middleware. So how should we implement authentication in. net core? As you wish, Microsoft has provided us with authentication middleware. The following uses CookieAuthenticationMiddleware as an example to describe how to implement authentication.

1. reference the Microsoft. AspNetCore. Authentication. Cookies package. In project practice, "Microsoft. AspNetCore. Authentication. Cookies": "1.1.0" is referenced ".

2. Register and configure authentication and authorization services in Startup:

Service Registration:

Services. addMvc (options =>{// Add the model to bind the filter options. filters. add (typeof (ModelValidateActionFilter); // Add an authorization filter to force Authentication jump and shield logic // var policy = new AuthorizationPolicyBuilder (). requireAuthenticatedUser (). build (); var policy = new AuthorizationPolicyBuilder (). addRequirements (new AuthenticationRequirement ()). build (); options. filters. add (new AuthorizeFilter (policy) ;}); // services. addAuthorization (options => // {// options. addPolicy ("RequireAuthentication", policy => policy. addRequirements (new AuthenticationRequirement ()));//});

Note that there are two comments in the above Code. Note 1: RequireAuthenticatedUser () is a pre-defined authorization verification for. net core. It indicates that the minimum requirement for authentication is to provide authenticated Identity. In the Demo, this is also my requirement. As long as it is a user with basic authentication, why is it not used in the Demo? This is a pitfall! In practice, I found that, in any case, the call always returns 401, And the download authentication and authorization source code are mandatory. The logic is as follows:

var user = context.User;            var userIsAnonymous =                user?.Identity == null ||                !user.Identities.Any(i => i.IsAuthenticated);            if (!userIsAnonymous)            {                context.Succeed(requirement);            }

When a breakpoint is added, it is found that IsAuthenticated is always false !!! We had to decompile the source code and find that the IsAuthenticated attribute of ClaimsIdentity is defined as follows:

WTF !!! What is this !!!. Net framework, remember the logic here is that as long as the Name is not empty, true will be returned, and it becomes in. net core, you say it is not a pitfall...

What should we do? Can't you give up? I think the first idea is to inherit ClaimsIdentity to customize an Identity, especially when I see the virtual attribute, I am no exception. After inheritance, I found that the authentication framework still does not recognize it, Or I still returned false. Maybe it is wrong where I use it. Therefore, the first comment in Startup appears. The final solution is to customize the AuthenticationRequirement and processor to verify the requirements as follows:

public class AuthenticationRequirement : AuthorizationHandler<AuthenticationRequirement>, IAuthorizationRequirement    {        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AuthenticationRequirement requirement)        {            var user = context.User;            var userIsAnonymous =                user?.Identity == null                || string.IsNullOrWhiteSpace(user.Identity.Name);            if (!userIsAnonymous)            {                context.Succeed(requirement);            }            return TaskCache.CompletedTask;        }    }

The red part of the above Code is the part that is changed by default.

In startup, the second part of the comment is to register an authorization policy. The registration method is also provided in the official website documentation. Why is it not used here? Because, if you configure by the method in the annotation, I need to use the Authorize flag on each controller or method that you want to authenticate, or even configure the role or policy on the feature, here, my default is global authentication. Therefore, it is directly added to the MVC processing pipeline in the form of a global filter. Read this, careful readers should have doubts. You have a simple authentication relationship with the authorization wool. What are the registration authorization filters! I also think it's okay. This is the second pitfall of net core certification, that is, in. in the view of net core or Microsoft, authentication only provides Principal generation, serialization, deserialization, and re-generation of Principal. Its responsibilities include returning various authentication failure information such as 401 and 403, however, this part will not be triggered automatically and must be triggered by other logic in the processing pipeline. I carefully read the documents on the official website and concluded that ,. net core probably believes that authentication is a diversified process. Not only do we see or need a certain type of authentication, but there may be multiple types of authentication in actual needs, our API may also allow multiple authentication methods at the same time, so if one authentication fails, 401 or 403 is returned directly. This is the second pitfall in practice! If authorization is added, this process can be triggered. The source code is used to detect the process. If the authorization fails, the filter returns a challengeResult, this Result will eventually go to the corresponding Challenge method in the authentication middleware. net core source code:

public async Task ChallengeAsync(ChallengeContext context)        {            ChallengeCalled = true;            var handled = false;            if (ShouldHandleScheme(context.AuthenticationScheme, Options.AutomaticChallenge))            {                switch (context.Behavior)                {                    case ChallengeBehavior.Automatic:                        // If there is a principal already, invoke the forbidden code path                        var result = await HandleAuthenticateOnceSafeAsync();                        if (result?.Ticket?.Principal != null)                        {                            goto case ChallengeBehavior.Forbidden;                        }                        goto case ChallengeBehavior.Unauthorized;                    case ChallengeBehavior.Unauthorized:                        handled = await HandleUnauthorizedAsync(context);                        Logger.AuthenticationSchemeChallenged(Options.AuthenticationScheme);                        break;                    case ChallengeBehavior.Forbidden:                        handled = await HandleForbiddenAsync(context);                        Logger.AuthenticationSchemeForbidden(Options.AuthenticationScheme);                        break;                }                context.Accept();            }            if (!handled && PriorHandler != null)            {                await PriorHandler.ChallengeAsync(context);            }        }

Take HandleForbiddenAsync as an example. The details are as follows:

/// <summary>        /// Override this method to deal with a challenge that is forbidden.        /// </summary>        /// <param name="context"></param>        protected virtual Task<bool> HandleForbiddenAsync(ChallengeContext context)        {            Response.StatusCode = 403;            return Task.FromResult(true);        }

In this way, the Challenge is triggered through the authorization process, and Challenge returns the corresponding verification result to the API caller.

 

After registering the required services for authentication and authorization, register the middleware as follows:

app.UseCookieAuthentication(new CookieAuthenticationOptions            {                AuthenticationScheme = "GuoKun",                AutomaticAuthenticate = true,                AutomaticChallenge = true,                DataProtectionProvider = DataProtectionProvider.Create(new DirectoryInfo(env.ContentRootPath))            });

app.UseMvc();

Note that UseCookieAuthentication should be placed before UseMvc. Pay attention to the red part. Why do you need to manually create DataProtectionProvider? This is a service cluster. If a single server or a single service instance is used, use the default DataProtection mechanism. In the code, manually specify the Directory Creation. the difference from the default implementation is that the default implementation will generate a key related to the current machine and Application for data encryption and decryption, and manually specify the directory to create the provider, A key xml file is generated under the specified directory. In this way, when the service cluster is deployed, the encryption and decryption keys are the same, and the decryption packets are the same. Don't ask me how I know it. If I step on the trap and try to debug it, and read the documents on the official website, my tears will burst...

3. Add a controller to simulate login and Authentication Authorization

[Route ("api/[controller]")] public class AccountController: Controller {[AllowAnonymous] [HttpPost ("login")] public async Task Login ([FromBody] User user) {IEnumerable <Claim> claims = new List <Claim> () {new Claim (ClaimTypes. name, user. UID)}; await HttpContext. authentication. signInAsync ("GuoKun", new ClaimsPrincipal (new ClaimsIdentity (claims);} [HttpGet ("serverresponse")] public ContentResult ServerResponse () {return this. content ($ "from {(Microsoft. aspNetCore. server. kestrel. internal. http. connectionContext) this. httpContext. features ). localEndPoint. response to ToString ()}: {this. user. identity. name ?? "Anonymous"}, hello ");}}

Because the authorization is now global, use AllowAnonymous to mark the logon method and skip authentication and authorization.

In the ServerResponse method, return the IP address and port number bound to the current service instance. Because this Demo uses ANCM to host in IIS, the port bound to the specific service instance is dynamic.

4. deployment. The specific deployment in IIS is as follows:

The ports of the three sites are 9003, and respectively. During running, ANCM will proxy IIS requests to KestrlServer.

5. Nginx Server Load balancer Configuration:

upstream guokun    {        server localhost:9001;        server localhost:9002;        server localhost:9003;    }    server {        listen       9000;        server_name  localhost;        #charset koi8-r;        #access_log  logs/host.access.log  main;        location / {            root   html;            index  index.html index.htm;            proxy_pass http://guokun;        }

This is simple and not nonsense.

6. Running effect:

Postman is used to simulate the request. When you directly request API/Account/serverresponse without calling the logon api:

As you can see, there is a Location in the Response Header, which is implemented by default in challenge and tells us to log on for authentication, after authentication, the url of the requested resource is redirected (especially useful in MVC ).

 

Next, log on:

We can see that the login is successful, and the server returns the encrypted and serialized creden. Next, we will request the api/Account/serverresponse:

 

No. The request is successful. The following results are obtained after multiple requests:

 

As you can see, the request has been loaded to different service instances.

Someone may ask why I don't deploy it on multiple different servers and simulate a machine there. Brother doesn't have that much money on so many machines. Moreover, the configuration of virtual machines cannot be supported.

 

In this way, a simple backend with authentication and Cluster load based on asp.net core is implemented.

 

 

Note:

Previously, due to network reasons, the ClaimsIdentity part did not download the source code, but was directly decompiled for viewing, resulting in ClaimsIdentity. isAuthenticated always returns the false conclusion, which is corrected here, and especially thanks to Savorboard's special correction. After reading the source code on Github, this attribute is defined as follows:

/// <summary>        /// Gets a value that indicates if the user has been authenticated.        /// </summary>        public virtual bool IsAuthenticated        {            get { return !string.IsNullOrEmpty(_authenticationType); }        }

If the previous error is "false", the authentication type is not specified when the ClaimsIdentity is successfully created. If you have figured out this, you can use the following method to register the corresponding authorization policy:

Services. addMvc (options =>{// Add the model to bind the filter options. filters. add (typeof (ModelValidateActionFilter); // Add an authorization filter to force Authentication redirect and shield the logic var policy = new AuthorizationPolicyBuilder (). requireAuthenticatedUser (). build (); // var policy = new AuthorizationPolicyBuilder (). addRequirements (new AuthenticationRequirement ()). build (); options. filters. add (new AuthorizeFilter (policy ));});

Correspondingly, after successful login, specify the AuthenticationType when building ClaimsIdentity:

await HttpContext.Authentication.SignInAsync("GuoKun",                new ClaimsPrincipal(new ClaimsIdentity(claims, "GuoKun")));

 

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.