10 minutes to learn the integrated jwt authority authentication of net core and quickly access the project

What is JWT

JSON Web Token (JWT) is the most popular cross domain authentication, distributed login, single sign on and other solutions.

JWT's official website address: https://jwt.io/

Generally speaking, JWT is a token that can represent the user's identity. You can use JWT token to verify the user's identity in the api interface to confirm whether the user has access to the api.

JWT contains the parameters necessary for identity authentication and user-defined parameters. JWT can use secret (using HMAC algorithm) or RSA or ECDSA's public / private key to sign.

What can JSON Web tokens do?

  1. Authorization: This is the most common scenario for using JWT. Once the user logs in, each subsequent request will include a JWT that allows the user to access the routes, services and resources allowed by the token. Single Sign On is a feature that is now widely used in JWT because it costs little and can be easily used in different domains.
  2. Information exchange: JSON Web tokens are a good way to securely transfer information between parties. Because JWT can sign - for example, using a public / private key pair - you can determine that the sender is the person they say. In addition, because you use headers and payloads to calculate signatures, you can verify that the content has not been tampered with.

How does a JSON Web token work?

In authentication, a JSON Web token is returned when a user successfully logs in using their credentials. Since tokens are credentials, great care must be taken to prevent security problems. In general, you should not keep the token longer than required.

Whenever a user wants to access a protected route or resource, the user agent should send the JWT using the bearer mode. Generally, in the Authorization header, the content of the title should be as follows:

Authorization: Bearer <token>

In some cases, this can be a stateless Authorization mechanism. The server's protected route checks for a valid JWT in the Authorization header  , If present, users are allowed to access protected resources. If the JWT contains the necessary data, you can reduce the need to query the database for certain operations, although this may not always be the case.

If a token is sent in the token Authorization header, cross domain resource sharing (CORS) will not be a problem because it does not use cookie s.

The following figure shows how to get the JWT and use it to access API s or resources:

1. The application requests authorization from the authorization server;

2. Verify the user identity. If the verification is successful, a token is returned;

3. Applications use access tokens to access protected resources.

The implementation of JWT is to store the user information in the client, and the server does not save it. Each request brings a token to verify the user's login status, so that the service becomes stateless and the server cluster is easy to expand.

 

For more theoretical knowledge, you can check the official website or the articles of relevant netizens. The following recommended articles:

net core integration jwt code implementation

New project

First, we create a new ASP.NET Core Web API project named jwtWebAPI, and select the target framework. Net core 3.1. Note that if https configuration is checked, ssl authentication must be removed when postman requests. It is recommended not to configure https.

Reference the jwt integrated package in nuget. Note that if you use the. NET Core 3.1 framework, the package version is 3.1.10

Microsoft.AspNetCore.Authentication.JwtBearer

 

Add a data access simulation api and create a new controller ValuesController

api/value1 can be accessed directly, and api/value2 adds the permission verification feature tag [Authorize]

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;

namespace jwtWebAPI.Controllers
{
    [ApiController]
    public class ValuesController : ControllerBase
    {
        [HttpGet]
        [Route("api/values1")]
        public ActionResult<IEnumerable<string>> values1()
        {
            return new string[] { "value1", "value1" };
        }

        /**
         * This interface uses the Authorize feature for permission verification. If it fails to pass the permission verification, the http return status code is 401
         * The correct posture for calling this interface is:
         * 1.Log in and call the api/Auth interface to get the token
         * 2.The api/value2 is in the head of the request. The request. The parameter is in the head of the request. What's going to be done. What's going to be done: the following is in the running of the running of the running of the running of the running of the running of the bill. The API API API / value1. The API api/value2. The api/value2. The api/value2. The api/value2. The api/value2. The api/value2 is in the head of the request. The parameter is added. The parameter is added in the head. What's going to be done. What's going going to be done in the head of the head of the request. What's going going to go for the way to go to add a parameter. Entitlement: what's going to go for what's going to do it it's going to do you's how to do it's how to do it's how to do it's how to do you do you do your job to do it's how to do your job to do it how ojuwmdailcjhdwqioijodhrwoi8vbg9jywxob3n0oj uwmdaifq.1s -40SrA4po2l4lB_ QdzON_ G5ZNT4P_ 6U25xhTcl7hI
         * Bearer Followed by a space, and followed by the token value returned by the interface in the first step
         * */
        [HttpGet]
        [Route("api/value2")]
        [Authorize]
        public ActionResult<IEnumerable<string>> value2()
        {
            //This is the way to get custom parameters
            var auth = HttpContext.AuthenticateAsync().Result.Principal.Claims;
            var userName = auth.FirstOrDefault(t => t.Type.Equals(ClaimTypes.NameIdentifier))?.Value;
            return new string[] { "Successful access: users who have logged in to this interface can access it", $"userName={userName}" };
        }

     
    }
}

Add an api to simulate login to generate Token, and create a new controller AuthController

Here is a simulation of login verification. It only verifies that the user password is not empty, that is, through verification, the logic of verifying the user and password is improved in the real environment.

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;

namespace jwtWebAPI.Controllers
{
    [ApiController]
    public class AuthController : Controller
    {
        /// <summary>
        /// Through account+Password acquisition Token
        /// </summary>
        /// <param name="userName"></param>
        /// <param name="pwd"></param>
        /// <returns>Token</returns>
        [AllowAnonymous]
        [HttpGet]
        [Route("api/auth")]
        public IActionResult GetToken(string userName, string pwd)
        {
            if (!string.IsNullOrEmpty(userName))
            {
                //Dynamic refresh every login
                Const.ValidAudience = userName + pwd + DateTime.Now.ToString();
                // push the user's name into a claim, so we can identify the user later on.
                //You can add custom parameters here at will, key You can get up on your own
                var claims = new[]
                {
                    new Claim(JwtRegisteredClaimNames.Nbf,$"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}") ,
                    new Claim (JwtRegisteredClaimNames.Exp,$"{new DateTimeOffset(DateTime.Now.AddMinutes(3)).ToUnixTimeSeconds()}"),
                    new Claim(ClaimTypes.NameIdentifier, userName)
                };
                //sign the token using a secret key.This secret will be shared between your API and anything that needs to check that the token is legit.
                var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Const.SecurityKey));
                var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
                //.NET Core's JwtSecurityToken class takes on the heavy lifting and actually creates the token.
                var token = new JwtSecurityToken(
                    //Issuer
                    issuer: Const.Domain,
                    //recipient
                    audience: Const.ValidAudience,
                    //Expiration time (can be set by yourself, pay attention to the above claims inside Exp (parameters are consistent)
                    expires: DateTime.Now.AddMinutes(3),
                    //Signing certificate
                    signingCredentials: creds,
                    //Custom parameters
                    claims: claims
                    );

                return Ok(new
                {
                    token = new JwtSecurityTokenHandler().WriteToken(token)
                });
            }
            else
            {
                return BadRequest(new { message = "username or password is incorrect." });
            }
        }
    }
}

Startup adds configuration related to JWT validation

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace jwtWebAPI
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            //add to jwt verification:
            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(options => {
                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateLifetime = true,//Is the expiration time verified
                        ClockSkew = TimeSpan.FromSeconds(30),  //Time offset (allowable error time)
                        ValidateAudience = true,//Verify Audience(Verify previous token (invalid)
                        //ValidAudience = Const.GetValidudience(),//Audience
                        //Dynamic verification is adopted here. When you log in again, refresh token,used token It's mandatory
                        AudienceValidator = (m, n, z) =>
                        {
                            return m != null && m.FirstOrDefault().Equals(Const.ValidAudience);
                        },
                        ValidateIssuer = true,//Verify Issuer((issuer)
                        ValidAudience = Const.Domain,//Audience    [Const Is the receiver of a new constant class 
                        ValidIssuer = Const.Domain,//Issuer,These two items are the same as those previously issued jwt The settings are consistent      Issuer
                        ValidateIssuerSigningKey = true,//Verify SecurityKey
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Const.SecurityKey))//Get the secret key SecurityKey
                    };
                    options.Events = new JwtBearerEvents
                    {
                        OnAuthenticationFailed = context =>
                        {
                            //Token expired
                            if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
                            {
                                context.Response.Headers.Add("Token-Expired", "true");
                            }
                            return Task.CompletedTask;
                        }
                    };
                });

            services.AddControllers();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        { 
            //add to jwt verification
            app.UseAuthentication();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}

Create constant class Const

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace jwtWebAPI
{
    public class Const
    {
        /// <summary>
        /// Here, for demonstration, write a key. The actual production environment can be read from the configuration file,This is a key randomly generated by online tools( md5 Or anything else)
        /// </summary>
        public const string SecurityKey = "48754F4C58F9EA428FE09D714E468211";

        /// <summary>
        /// Site address (issuer and recipient). The test here is the same as the current local running website. The actual address sent to the formal environment should be the domain name address
        /// </summary>
        public const string Domain = "https://localhost:44345";

        /// <summary>
        /// The reason why the receiver is made variable is to use the interface to dynamically change this value to simulate coercion Token invalid
        /// The real business scenario can be in the database or redis Save a and user id Related values, generating token And verification token Get the persistent value for verification
        /// If you log in again, refresh this value
        /// </summary>
        public static string ValidAudience;
    }
}

JWT login authorization test succeeded

Compile and run the program, open postman, enter the address, and test the program without any authorization first

 

 

If the data is returned correctly, let's test the flow of JWT.

First, we don't call the interface at all: https://localhost:44345/api/values2 , please note that when I created it, it was HTTPS. Please pay attention to whether it is http or HTTPS

 

Status code 401 is returned, that is, unauthorized: access is denied due to invalid credentials. It indicates that JWT verification has taken effect and our interface has been protected.

Call the simulated Login authorization interface: https://localhost:44345/api/auth?userName=xiongze&pwd=123456

The user password here is written casually, because we simulated Login and only verified that the next password is not empty, so anything written can pass.

 

Then we get a token value in xxx.yyy.zzz format. We copy the token.

In the interface( https://localhost:44345/api/values2 )Add JWT parameters to the request header and add our token

Call our analog data interface again, but this time we add a header, KEY: Authorization       Value: the value of Bearer Tokne

Here we need to pay attention   There is a space after Bearer, and then the token we obtained in the previous step,

 

The returned value is obtained and the authorization is successful. We support custom return parameters. There are relevant contents in the above code, such as user name, which can be returned with insensitive information.

When the expiration time set for the token expires, or a new token is regenerated and not updated in time, our authorization also expires, 401,

Upgrade operation: interface permission isolation

The above operation is that all roles with successful login authorization can call all interfaces. Now we want to restrict interface isolation,

In other words, although I am authorized to log in, my interface is accessed with specified permissions.

For example, deleting an interface can only be operated by the administrator role. Although other roles are authorized to log in, they do not have permission to call the deleting interface.

Let's take a look at the transformation and upgrading of the original operation.

Add class

Create a new AuthManagement folder, add PolicyRequirement class and PolicyHandler class,

PolicyRequirement class:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace jwtWebAPI.AuthManagement
{
    /// <summary>
    /// Permission hosting entity
    /// </summary>
    public class PolicyRequirement : IAuthorizationRequirement
    {
        /// <summary>
        /// User rights collection
        /// </summary>
        public List<UserPermission> UserPermissions { get; private set; }
        /// <summary>
        /// No permission action
        /// </summary>
        public string DeniedAction { get; set; }
        /// <summary>
        /// structure
        /// </summary>
        public PolicyRequirement()
        {
            //Jump to this route without permission
            DeniedAction = new PathString("/api/nopermission");
            //Routing configuration to which the user has access,Of course, it can be obtained from the database
            UserPermissions = new List<UserPermission> {
                              new UserPermission {  Url="/api/values3", UserName="admin"},
                          };
        }
    }

    /// <summary>
    /// User rights hosting entity
    /// </summary>
    public class UserPermission
    {
        /// <summary>
        /// user name
        /// </summary>
        public string UserName { get; set; }
        /// <summary>
        /// request Url
        /// </summary>
        public string Url { get; set; }
    }
}

PolicyHandler class (note the difference between 2.x and 3.x)

 

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;

namespace jwtWebAPI.AuthManagement
{
    public class PolicyHandler : AuthorizationHandler<PolicyRequirement>
    {
        private readonly IHttpContextAccessor _httpContextAccessor;
        public PolicyHandler(IHttpContextAccessor httpContextAccessor)
        {
            _httpContextAccessor = httpContextAccessor;
        }
        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PolicyRequirement requirement)
        {
            //Assign user permissions
            var userPermissions = requirement.UserPermissions;
            var httpContext = _httpContextAccessor.HttpContext;

            //request Url
            var questUrl = httpContext.Request.Path.Value.ToUpperInvariant();
            //Is it verified
            var isAuthenticated = httpContext.User.Identity.IsAuthenticated;
            if (isAuthenticated)
            {
                if (userPermissions.GroupBy(g => g.Url).Any(w => w.Key.ToUpperInvariant() == questUrl))
                {
                    //user name
                    var userName = httpContext.User.Claims.SingleOrDefault(s => s.Type == ClaimTypes.NameIdentifier).Value;
                    if (userPermissions.Any(w => w.UserName == userName && w.Url.ToUpperInvariant() == questUrl))
                    {
                        context.Succeed(requirement);
                    }
                    else
                    {
                        ////No permission to jump to the reject page
                        //httpContext.Response.Redirect(requirement.DeniedAction);
                        return Task.CompletedTask;
                    }
                }
                else
                {
                    context.Succeed(requirement);
                }
            }
            return Task.CompletedTask;
        }
    }
}

Add specified role

stay   The GetToken authorization of AuthController adds custom parameters, as follows

new Claim("Role", userName)   // Here is the role. I use the login account admin instead

 

stay   AuthController   Add unauthorized access methods to the controller

[AllowAnonymous]
[HttpGet]
[Route("api/nopermission")]
public IActionResult NoPermission()
{
     return Forbid("No Permission!");
}

 

Modify Startup configuration

Add policy authentication mode, JWT Scheme and authorization Handler in the ConfigureServices method of startup.cs  

The revised document is as follows

using jwtWebAPI.AuthManagement;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace jwtWebAPI
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services
                //Add policy authentication mode
                .AddAuthorization(options =>
                {
                    options.AddPolicy("Permission", policy => policy.Requirements.Add(new PolicyRequirement()));
                })
                //add to JWT Scheme
                .AddAuthentication(s =>
                {
                    s.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                    s.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
                    s.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                })
                //add to jwt verification:
                .AddJwtBearer(options => {
                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateLifetime = true,//Is the expiration time verified
                        ClockSkew = TimeSpan.FromSeconds(30),  //Time offset (allowable error time)
                        ValidateAudience = true,//Verify Audience(Verify previous token (invalid)
                        //ValidAudience = Const.GetValidudience(),//Audience
                        //Dynamic verification is adopted here. When you log in again, refresh token,used token It's mandatory
                        AudienceValidator = (m, n, z) =>
                        {
                            return m != null && m.FirstOrDefault().Equals(Const.ValidAudience);
                        },
                        ValidateIssuer = true,//Verify Issuer((issuer)
                        ValidAudience = Const.Domain,//Audience    [Const Is the receiver of a new constant class 
                        ValidIssuer = Const.Domain,//Issuer,These two items are the same as those previously issued jwt The settings are consistent      Issuer
                        ValidateIssuerSigningKey = true,//Verify SecurityKey
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Const.SecurityKey))//Get the secret key SecurityKey
                    };
                    options.Events = new JwtBearerEvents
                    {
                        OnAuthenticationFailed = context =>
                        {
                            //Token expired
                            if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
                            {
                                context.Response.Headers.Add("Token-Expired", "true");
                            }
                            return Task.CompletedTask;
                        }
                    };
                });

            //Injection authorization Handler
            services.AddSingleton<IAuthorizationHandler, PolicyHandler>();
            //Injection acquisition HttpContext
            services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
            services.AddControllers();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        { 
            //add to jwt verification
            app.UseAuthentication();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
            
        }
    }
}

 

Add api access methods

stay   The ValuesController controller adds the access method of the specified permission, as follows:

        /**
        * This interface must use admin
        **/
        [HttpGet]
        [Route("api/values3")]
        [Authorize("Permission")]
        public ActionResult<IEnumerable<string>> values3()
        {
            //This is the way to get custom parameters
            var auth = HttpContext.AuthenticateAsync().Result.Principal.Claims;
            var userName = auth.FirstOrDefault(t => t.Type.Equals(ClaimTypes.NameIdentifier))?.Value;
            var role = auth.FirstOrDefault(t => t.Type.Equals("Role"))?.Value;

            return new string[] { "Access succeeded: this interface can only be accessed with administrator privileges", $"userName={userName}", $"Role={role}" };
        }

Test access with different permissions

We simulate login in the same way, https://localhost:44345/api/auth?userName=xiongze&pwd=123

Note that the account does not need to log in with admin first, and then use the returned token to request the interface with the specified permission we just added. At this time, there is no permission to access, because this is admin permission access.

We simulate login in the same way, https://localhost:44345/api/auth?userName=admin&pwd=123

Access succeeded.

Source download address

Gitee: https://gitee.com/xiongze/jwtWebAPI.git

reference

 

 
Welcome to subscribe to WeChat public official account.
Author: Xiong Ze - pain and joy in learning
Official account: Bear's saying
source:   https://www.cnblogs.com/xiongze520/p/15540035.html
You can reprint and excerpt at will, but please indicate the author and the link to the original text in the article.  

 

 

 

Tags: jwt

Posted on Thu, 11 Nov 2021 21:33:34 -0500 by trukfixer