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?- 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.
- 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.
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:
- asp.net core integrated JWT (I): https://www.cnblogs.com/7tiny/archive/2019/06/13/11012035.html
- Five minutes to show you what JWT is: https://zhuanlan.zhihu.com/p/86937325
- C# distributed login - jwt: https://www.cnblogs.com/yswenli/p/13510050.html
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=" }; } } }
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 Claim (JwtRegisteredClaimNames.Exp,$""), 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 isolationThe 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=", $"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 addressGitee: https://gitee.com/xiongze/jwtWebAPI.git
reference- asp.net core integrated JWT (I): https://www.cnblogs.com/7tiny/archive/2019/06/13/11012035.html
- Five minutes to show you what JWT is: https://zhuanlan.zhihu.com/p/86937325
- C# distributed login - jwt: https://www.cnblogs.com/yswenli/p/13510050.html
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.