当前位置:网站首页>ASP. Net core JWT certification

ASP. Net core JWT certification

2022-04-23 17:10:00 begeneral

ASP.NET CORE edition :.NET 5.0 

About JWT Basic knowledge of , Please search the Internet , This is mainly explained from the code level .

First, create a new one ASP.NET CORE Web api Empty project for , And then create a new one LoginController Of api controller .

from nuget Installation package in :Microsoft.AspNetCore.Authentication.JwtBearer;

1、 Generate Token

Look at the code first :

    [Route("api/[controller]/[action]")]
    [ApiController]
    public class LoginController : Controller
    {
        private readonly PermissionRequirement _permissionRequirement;
        public LoginController(PermissionRequirement permissionRequirement)
        {
            _permissionRequirement = permissionRequirement;
        }
        [HttpGet]
        public IActionResult GetJwtToken(string userName, string password)
        {
            var claims = new List<Claim> {
                        new Claim(ClaimTypes.GivenName, "mark"),
                        new Claim(ClaimTypes.Name, userName),
                        new Claim(JwtRegisteredClaimNames.Jti, "10000") };
            var roles = new List<Claim>();
            if (userName == "ncy")
            {
                roles.Add(new Claim(ClaimTypes.Role, "admin"));
            }
            else
            {
                roles.Add(new Claim(ClaimTypes.Role, "normal"));
            }
            claims.AddRange(roles);
            var token = JwtToken.BuildJwtToken(claims.ToArray(), _permissionRequirement);
            var jm = new RespEntity();
            jm.code = 0;
            jm.data = token;
            jm.msg = "success";

            return Json(jm);
        }
    }

I add different roles according to the entered user name here for role permission control , You don't have to pay attention to this .

PermissionRequirement It is a class composed of parameter fields required for authentication , The definition is as follows :

    public class PermissionRequirement : IAuthorizationRequirement
    {
        /// <summary>
        ///  User privilege set , An order contains many details ,
        ///  Empathy , The certification of a website is being issued , There are also many permission details ( Here is Role and URL The relationship between )
        /// </summary>
        public List<PermissionItem> Permissions { get; set; }
        /// <summary>
        ///  No authority action
        /// </summary>
        public string DeniedAction { get; set; }

        /// <summary>
        ///  Authentication authorization type 
        /// </summary>
        public string ClaimType { internal get; set; }
        /// <summary>
        ///  Request path 
        /// </summary>
        public string LoginPath { get; set; } = "/Api/Login";
        /// <summary>
        ///  The issuer 
        /// </summary>
        public string Issuer { get; set; }
        /// <summary>
        ///  Subscribe to the people 
        /// </summary>
        public string Audience { get; set; }
        /// <summary>
        ///  Expiration time 
        /// </summary>
        public TimeSpan Expiration { get; set; }
        /// <summary>
        ///  Signature verification 
        /// </summary>
        public SigningCredentials SigningCredentials { get; set; }


        /// <summary>
        ///  structure 
        /// </summary>
        /// <param name="deniedAction"> Refusing a contract request url</param>
        /// <param name="permissions"> Privilege set </param>
        /// <param name="claimType"> Declaration type </param>
        /// <param name="issuer"> The issuer </param>
        /// <param name="audience"> Subscribe to the people </param>
        /// <param name="signingCredentials"> Signature verification entity </param>
        /// <param name="expiration"> Expiration time </param>
        public PermissionRequirement(string deniedAction, List<PermissionItem> permissions, string claimType, string issuer, string audience, SigningCredentials signingCredentials, TimeSpan expiration)
        {
            ClaimType = claimType;
            DeniedAction = deniedAction;
            Permissions = permissions;
            Issuer = issuer;
            Audience = audience;
            Expiration = expiration;
            SigningCredentials = signingCredentials;
        }
    }

Actually generate token Function of :

    public static string BuildJwtToken(Claim[] claims, PermissionRequirement                 
    permissionRequirement)
        {
            var now = DateTime.Now;
            //  Instantiation JwtSecurityToken
            var jwt = new JwtSecurityToken(
                issuer: permissionRequirement.Issuer,
                audience: permissionRequirement.Audience,
                claims: claims,
                notBefore: now,
                expires: now.Add(permissionRequirement.Expiration),
                signingCredentials: permissionRequirement.SigningCredentials
            );
            //  Generate  Token
            var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
            return encodedJwt;
        }

So let's see Claim This class . This class is actually JWT Load of , The message body . We are instantiating a Claim Is passed in 2 Parameters , One type, One value, It's actually a key value pair . We turn on jwt analysis token Website :JSON Web Tokens - jwt.io, Make us into token Copy to the text box on the left , The screenshot is as follows :

 payload In front of 3 Whether we use a key value pair Claim Defined ( there type Is string )? As for the following key value pairs , Let's look at the function BuildJwtToken, These values are in PermissionRequirement Assigned in .

2、ClaimTypes.Role

new Claim(ClaimTypes.Role, "admin"). Write the role information of the current user into the load . I am here. admin It's the role of testing , The actual role should be after the user logs in , Get the role of the user from the database .

There is a benefit of writing... Into a character : When verifying role permissions , There is no need to query the role of the current user from the database , Reduce the number of database queries . But there are several disadvantages :

1、 If the user has many roles , It can lead to token Bigger , And every time the client interacts with the server, it sends token, Therefore, it will lead to the increase of network traffic .

2、 If the user's role is modified , Then the user must log in again . because JWT Token It's disposable , You must log in again to get a new token.

For this question , I think the better plan is : Store the user's role information in the cache ( such as redis), Then update the cache every time you modify the user's role information .

3、 Security

We just generated token Put in jwt Official token The content of load can be analyzed in the analysis tool , That explains it jwt There are security problems . Actually jwt Just for payload Did base64 code , As long as the load part is base64 Decoding can get the content of the load . So we can't put important information in paylaod Inside .

4、 add to JWT Certification services

The following code is written in startup Class ConfigureServices Function , Used to register JWT authentication

            var keyBytes = Encoding.Default.GetBytes("MyJwtWebTokenApplication");
            var signingKey = new SymmetricSecurityKey(keyBytes);
            var issuer = "CoreShop";
            var audience = "CoreCms";
            //  Token validation parameters 
            var tokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,   // Whether the validation SecurityKey
                IssuerSigningKey = signingKey,  // Get SecurityKey
                ValidateIssuer = true, // Whether the validation Issuer
                ValidIssuer = issuer,// The issuer  //Issuer, These two and the front sign jwt The Settings are consistent 
                ValidateAudience = true, // Whether the validation Audience
                ValidAudience = audience,// Subscribe to the people 
                ValidateLifetime = true,// Is the failure time verified 
                ClockSkew = TimeSpan.FromSeconds(0),
                RequireExpirationTime = true,
            };
            services.AddAuthentication(x =>
            {
                x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                x.DefaultForbidScheme = JwtBearerDefaults.AuthenticationScheme;
            }).AddJwtBearer(o =>
            {
                o.TokenValidationParameters = tokenValidationParameters;
                o.Events = new JwtBearerEvents
                 {
                     OnChallenge = context =>
                     {
                         context.Response.Headers.Add("Token-Error", context.ErrorDescription);
                         return Task.CompletedTask;
                     },
                     OnAuthenticationFailed = context =>
                     {
                         var token = context.Request.Headers["Authorization"].ObjectToString().Replace("Bearer ", "");
                         var jwtToken = (new JwtSecurityTokenHandler()).ReadJwtToken(token);

                         if (jwtToken.Issuer != issuer)
                         {
                             context.Response.Headers.Add("Token-Error-Iss", "issuer is wrong!");
                         }

                         if (jwtToken.Audiences.FirstOrDefault() != audience)
                         {
                             context.Response.Headers.Add("Token-Error-Aud", "Audience is wrong!");
                         }

                         //  If expired , Then put < Is it overdue > Add to , Back to the header 
                         if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
                         {
                             context.Response.Headers.Add("Token-Expired", "true");
                         }
                         return Task.CompletedTask;
                     }
                 };
            });

Several constant strings here should be defined in the configuration file , For convenience , I wrote it directly here .

Note that the key length here must be greater than or equal to 16 Bytes , Otherwise, it will be generated token I will make a mistake

Token validation parameters ClockSkew Parameters ( Default clock offset field ), You are generating token When the specified expiration time is added with this deviation time, it is token The expiration time of . Its default value is 300 second , The definition is as follows :

  This place feels a little pit , Everyone should be careful .

The next step is to specify the authentication scheme , The default solution is Bearer. When using this scheme , The result of certification is JWT The default return result . Mainly reflected in the returned status code , If the certification is successful , The status code is 200. Failure (Challenge) Namely 401. Authentication succeeded but no authorization (Forbid) Namely 403.

In addition to using the default scheme , We can also customize the scheme . Define a class inheritance AuthenticationHandler<AuthenticationSchemeOptions>, Then rewrite the return result you want to customize (Authenticate、Challenge、Forbid), The code is as follows :

x.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = nameof(ApiResponseForAdminHandler);
x.DefaultForbidScheme = nameof(ApiResponseForAdminHandler);
public class ApiResponseForAdminHandler : AuthenticationHandler<AuthenticationSchemeOptions>
    {
        public ApiResponseForAdminHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)
        {
        }

        protected override Task<AuthenticateResult> HandleAuthenticateAsync()
        {
            throw new NotImplementedException();
        }
        protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
        {
            Response.ContentType = "application/json";    
            var jm = new AdminUiCallBack();
            jm.code = 401;
            jm.data = 401;
            jm.msg = " I'm sorry , You do not have access to this interface , Please make sure you are logged in !";
            await Response.WriteAsync(JsonConvert.SerializeObject(jm));
        }

        protected override async Task HandleForbiddenAsync(AuthenticationProperties properties)
        {
            Response.ContentType = "application/json";        
            var jm = new AdminUiCallBack();
            jm.code = 403;
            jm.msg = " I'm sorry , Your access level is not enough , Contact administrator !!";
            await Response.WriteAsync(JsonConvert.SerializeObject(jm));

        }

    }

there HandleAuthenticateAsync We directly throw an exception that is not implemented , Because in AddAuthentication when DefaultAuthenticateScheme=JwtBearerDefaults.AuthenticationScheme; So this function will not be called . But the following challenge and forbidden Is to use this class as its scheme .

In this case , Even if the certification is not successful , The returned status code is also 200, We need to go from AdminUiCallBack Get the result of authentication in class .

However, when using custom schemes , You need to add a line of code , Specifies the class that provides the scheme

.AddScheme<AuthenticationSchemeOptions, ApiResponseForAdminHandler>(nameof(ApiResponseForAdminHandler), o => { })

This is AddJwtBearer Call after the function . Here's one thing to pay attention to , A custom scheme is used ,JwtBearerEvents The event delegate defined in it will become invalid , So here's one of two . If the solution is too complex , You can use custom schemes , conversely , You can use event delegates to handle . If you use an event delegate , You don't need to call AddScheme Function .

Last in Configure Call in function app.UseAuthentication(); Enable the authentication middleware .

5、C/S Client side authentication

webapi not only b/s The front end can call ,c# winform You can also call

            HttpClient client = new HttpClient();
            client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _token);
            var response = await client.GetAsync("http://localhost:5000/api/test/GetMyAddress");
            if (response.StatusCode == System.Net.HttpStatusCode.OK)
            {
                var content = await response.Content.ReadAsStringAsync();
                Console.WriteLine($"api Return the data :{content}");
            }
            else
            {
                Console.WriteLine($" request api Interface failed , Status code :{((int)response.StatusCode)}");
            }

Please take a look at the second line of code , stay new One AuthenticationHeaderValue Object time , One parameter is the authentication scheme , One is token Value , This certification scheme must bring .

Parsing on the server side token You should also put Bearer Remove this string to parse token

版权声明
本文为[begeneral]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/04/202204230554081782.html