preface
This article will introduce Authentication and Authorization respectively.
And with a simple example in ASP Net core 6.0 implements these two functions in the web API.
Related nouns
Authentication and Authorization look very much like each other. It's hard to distinguish them.
Authentication: identify the user's identity, which usually occurs when logging in.
Authorization: Grant user permission and specify which resources the user can access; The premise of authorization is to know who the user is, so authorization must be after authentication.
Authentication
Basic steps
- Install related Nuget packages: Microsoft AspNetCore. Authentication. JwtBearer
- Prepare configuration information (key, etc.)
- Add service
- Call Middleware
- Implement a JwtHelper to generate a Token
- Controller restrict access (add Authorize tag)
1 install Nuget package
Install Microsoft AspNetCore. Authentication. JwtBearer
In the package manager console:
Install-Package Microsoft.AspNetCore.Authentication.JwtBearer -Version 6.0.1
2 prepare configuration information
In appsetting JSON, add a Jwt node
"Jwt": { "SecretKey": "lisheng741@qq.com", "Issuer": "WebAppIssuer", "Audience": "WebAppAudience" }
3 add service
In program Register the service in the. CS file.
// Introduce the required namespace using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; using System.Text; // ...... var configuration = builder.Configuration; // Registration service builder.Services.AddAuthentication(options => { options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters() { ValidateIssuer = true, //Verify Issuer ValidIssuer = configuration["Jwt:Issuer"], //Issuer issuer ValidateAudience = true, //Verify Audience ValidAudience = configuration["Jwt:Audience"], //Subscriber Audience ValidateIssuerSigningKey = true, //Verify SecurityKey IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["Jwt:SecretKey"])), //SecurityKey ValidateLifetime = true, //Is the expiration time verified ClockSkew = TimeSpan.FromSeconds(30), //Fault tolerance value of expiration time to solve the problem of server-side time synchronization (seconds) RequireExpirationTime = true, }; });
4 call Middleware
Calling UseAuthentication (authentication) must be called top note before any authentication middleware is needed, such as UseAuthorization.
// ...... app.UseAuthentication(); app.UseAuthorization(); // ......
5 JwtHelper class implementation
It is mainly used to generate the Token of JWT.
using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; namespace TestWebApi; public class JwtHelper { private readonly IConfiguration _configuration; public JwtHelper(IConfiguration configuration) { _configuration = configuration; } public string CreateToken() { // 1. Define the Claims to be used var claims = new[] { new Claim(ClaimTypes.Name, "u_admin"), //HttpContext.User.Identity.Name new Claim(ClaimTypes.Role, "r_admin"), //HttpContext.User.IsInRole("r_admin") new Claim(JwtRegisteredClaimNames.Jti, "admin"), new Claim("Username", "Admin"), new Claim("Name", "Super administrator") }; // 2. From Appsettings JSON var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:SecretKey"])); // 3. Select encryption algorithm var algorithm = SecurityAlgorithms.HmacSha256; // 4. Generate Credentials var signingCredentials = new SigningCredentials(secretKey, algorithm); // 5. Generate a token according to the above var jwtSecurityToken = new JwtSecurityToken( _configuration["Jwt:Issuer"], //Issuer _configuration["Jwt:Audience"], //Audience claims, //Claims, DateTime.Now, //notBefore DateTime.Now.AddSeconds(30), //expires signingCredentials //Credentials ); // 6. Change token to string var token = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken); return token; } }
The JwtHelper relies on IConfiguration (to read the configuration file), and the creation of JwtHelper is handed over to DI container in program Add service to CS:
var configuration = builder.Configuration; builder.Services.AddSingleton(new JwtHelper(configuration));
Register JwtHelper as singleton mode.
6 controller configuration
Create a new AccountController, inject JwtHelper in the form of constructor, and add two actions: GetToken to obtain Token, and GetTest is labeled with [Authorize] to verify authentication.
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace TestWebApi.Controllers; [Route("api/[controller]/[action]")] [ApiController] public class AccountController : ControllerBase { private readonly JwtHelper _jwtHelper; public AccountController(JwtHelper jwtHelper) { _jwtHelper = jwtHelper; } [HttpGet] public ActionResult<string> GetToken() { return _jwtHelper.CreateToken(); } [Authorize] [HttpGet] public ActionResult<string> GetTest() { return "Test Authorize"; } }
7 test call
Method 1: debug software through interfaces such as Postman and Apifox
Use Postman to call / api/Account/GetToken to generate a Token
When calling / api/Account/GetTest, pass in the Token and get the returned result
Mode 2: debug on browser console
Debug / api/Account/GetToken
var xhr = new XMLHttpRequest(); xhr.addEventListener("readystatechange", function() { if(this.readyState === 4) { console.log(token = this.responseText); //A global variable token is used here to serve the next interface } }); xhr.open("GET", "/api/Account/GetToken"); xhr.send();
Debug / api/Account/GetTest
var xhr = new XMLHttpRequest(); xhr.addEventListener("readystatechange", function() { if(this.readyState === 4) { console.log(this.status, this.responseText); //this.status is the response status code and 401 is the non authentication status } }); xhr.open("GET", "/api/Account/GetTest"); xhr.setRequestHeader("Authorization",`Bearer ${token}`); //Attach a token xhr.send();
Authorization
Note: authorization must be based on authentication, that is, if the above configuration on authentication is not completed, the following authorization will not succeed.
In the authorization part, we will first introduce relevant labels and authorization methods, and then introduce policy based authorization. The general contents of these three parts are described as follows:
Related tags: Authorize and AllowAnonymous
Authorization method: introduce the basic contents of Policy, Role and Scheme
Policy based authorization: in depth policy authorization method
Related label (Attribute)
Please refer to the official documents for the specific authorization related labels Simple authorization
[Authorize]
The Controller or Action marked with this label must be authenticated and can identify which authorization rules need to be met.
Authorization rules can be policies, Roles, or AuthenticationSchemes.
[Authorize(Policy = "", Roles ="", AuthenticationSchemes ="")]
[AllowAnonymous]
Anonymous access is allowed, and the level is higher than [Authorize]. If both work at the same time, it will take effect [AllowAnonymous]
Authorization method
Basically, there are only three authorization methods: Policy, Role and Scheme, which correspond to the three attributes of the Authorize tag.
1 Policy
The recommended authorization method is in ASP Net core is the most mentioned official document. A Policy can contain multiple requirements (the requirements may be Role matching, Claims matching, or other methods.)
The following is a basic example (it is a basic example, mainly a Policy based authorization method, and some configurations can be added continuously):
In program CS, add two policies:
policy1 requires the user to have a Claim whose Claim type value is EmployeeNumber.
policy2 requires the user to have a Claim whose ClaimType value is EmployeeNumber and whose ClaimValue value is 1, 2, 3, 4 or 5.
builder.Services.AddAuthorization(options => { options.AddPolicy("policy1", policy => policy.RequireClaim("EmployeeNumber")); options.AddPolicy("policy2", policy => policy.RequireClaim("EmployeeNumber", "1", "2", "3", "4", "5")); })
Add the [Authorize] tag to the controller to take effect:
[Authorize(Policy = "policy1")] public class TestController : ControllerBase
Or on the Action of the controller:
public class TestController : ControllerBase { [Authorize(Policy = "policy1")] public ActionResult<string> GetTest => "GetTest"; }
2 Role
Based on role authorization, users can pass authorization verification as long as they have roles.
When authenticating, add a Claim related to the role to identify the role owned by the user (Note: a user can have multiple role claims), such as:
new Claim(ClaimTypes.Role, "admin"), new Claim(ClaimTypes.Role, "user")
In Controller or Action:
[Authorize(Roles = "user")] public class TestController : ControllerBase { public ActionResult<string> GetUser => "GetUser"; [Authorize(Roles = "admin")] //Superimposed with the Authorize function of the controller, in addition to having user, you also need to have admin public ActionResult<string> GetAdmin => "GetAdmin"; [Authorize(Roles = "user,admin")] //Either user or admin can be satisfied public ActionResult<string> GetUserOrAdmin => "GetUserOrAdmin"; }
3 Scheme
Schemes such as Cookies and Bearer can also be customized.
Since this method is not commonly used, it is not expanded here. Please refer to the official documents Limit identification by scheme.
Policy based authorization
A basic example of policy based authorization has been mentioned above. We will continue to explore this authorization method.
1 authorization process
Before going deeper into the authorization of Policy, it is necessary to describe the authorization process. The description of the authorization process is suggested to be viewed in combination with the source code, so as to better understand its role. Of course, this part is difficult to understand, and the author's statement may not be clear enough, and this part will not have an impact on the completion of authorization configuration. Therefore, if the reader can't understand or understand it, he can skip it for the time being without entanglement.
I suggest you take a look ASP.NET Core authentication and authorization 6: how is the authorization policy implemented? In this article, the source code related to authorization is sorted out, and the relationship between them is explained.
Here is a brief summary:
The interface s and class es related to authorization are as follows:
IAuthorizationService #The main method of verifying authorized services is AuthorizeAsync DefaultAuthorizationService #Default implementation of IAuthorizationService IAuthorizationHandler #Be responsible for checking whether the requirements are met. The main method is HandleAsync IAuthorizationRequirement #Only attributes, no methods; A mechanism for marking services and for tracking the success of authorization. AuthorizationHandler<TRequirement> #Main method HandleRequirementAsync
The relationship between these interface s and class es and the authorization process are as follows:
DefaultAuthorizationService implements the AuthorizeAsync method of IAuthorizationService.
The AuthorizeAsync method will obtain all instances that implement IAuthorizationHandler, and call the HandleAsync method of all instances to check whether the authorization requirements are met. If any HandleAsync returns Fail, the cycle will end (please refer to the official document for details) Result returned by handler )And prohibit user access.
IAuthorizationHandler functions as described above, providing a HandleAsync method to check authorization.
IAuthorizationRequirement is a requirement, which is mainly used in conjunction with the authorization handler < trequirement >.
Authorizationhandler < TRequirement > implements the HandleAsync method of IAuthorizationHandler and provides a HandleRequirementAsync method. HandleRequirementAsync is used to check whether the requirements are met. The default implementation of HandleAsync is to obtain all requests that implement trequirements (and the request is added to the list by Policy). Call HandleRequirementAsync circularly to check which requirements can meet the authorization.
Briefly:
When the [Authorize] tag takes effect, the AuthorizeAsync of IAuthorizationService (implemented by DefaultAuthorizationService) is called.
AuthorizeAsync will call HandleAsync of all iauthorizationhandlers (implemented by authorizationhandler < trequirement >).
HandleAsync will call the method of HandleRequirementAsync of authorizationhandler < trequirement >.
Note: only the main interfaces and classes are listed here, but some are not listed, such as IAuthorizationHandlerProvider (the default implementation of this interface is DefaultAuthorizationHandlerProvider, which is mainly used to collect IAuthorizationHandler and return IEnumerable < IAuthorizationHandler >)
2 implementation description
IAuthorizationService has been implemented by default and no extra work is required.
IAuthorizationHandler is implemented by authorizationhandler < trequirement >.
So what we need to do is:
The first step is to prepare requirements and implement iauthorization requirements
Step 2: add a Handler program to inherit the authorizationhandler < trequirement > and override the HandleRequirementAsync method
For specific implementation, you can refer to ASP.NET Core authentication and authorization 7: dynamic authorization Based on the authorization of authority, the idea of this article has been very clear, and the main steps are listed here.
3. Define permission items
Before implementing the Requirement, we need to define some permission items, which are mainly used as the name of Policy and passed into the Requirement we implement.
public static class UserPermission { public const string User = "User"; public const string UserCreate = User + ".Create"; public const string UserDelete = User + ".Delete"; public const string UserUpdate = User + ".Update"; }
As above, permissions such as "add", "delete" and "modify" are defined, in which User will have full permissions.
4. Implement requirements
public class PermissionAuthorizationRequirement : IAuthorizationRequirement { public PermissionAuthorizationRequirement(string name) { Name = name; } public string Name { get; set; } }
Use the Name attribute to represent the Name of the permission, which corresponds to the constant in UserPermission.
5 implement authorization Handler
Here, it is assumed that the ClaimType in the user's Claim is Permission, such as:
new Claim("Permission", UserPermission.UserCreate), new Claim("Permission", UserPermission.UserUpdate)
As above, identify the permissions of the user UserCreate and UserUpdate.
Note: of course, the actual program is certainly not implemented in this way. Here is just a simple example.
Next, implement an authorization Handler:
public class PermissionAuthorizationHandler : AuthorizationHandler<PermissionAuthorizationRequirement> { protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionAuthorizationRequirement requirement) { var permissions = context.User.Claims.Where(_ => _.Type == "Permission").Select(_ => _.Value).ToList(); if (permissions.Any(_ => _.StartsWith(requirement.Name))) { context.Succeed(requirement); } return Task.CompletedTask; } }
When running HandleRequirementAsync, the item with ClaimType of Permission in the user's Claim will be taken out and its Value will be obtained to form a list < string >.
Then verify whether the requirements meet the authorization requirements, and run context Succeeded.
6 add authorization handler
In program CS, add PermissionAuthorizationHandler to DI:
builder.Services.AddSingleton<IAuthorizationHandler, PermissionAuthorizationHandler>();
7 add authorization policy
builder.Services.AddAuthorization(options => { options.AddPolicy(UserPermission.UserCreate, policy => policy.AddRequirements(new PermissionAuthorizationRequirement(UserPermission.UserCreate))); options.AddPolicy(UserPermission.UserUpdate, policy => policy.AddRequirements(new PermissionAuthorizationRequirement(UserPermission.UserUpdate))); options.AddPolicy(UserPermission.UserDelete, policy => policy.AddRequirements(new PermissionAuthorizationRequirement(UserPermission.UserDelete))); });
8 controller configuration
The controller is as follows:
[Route("api/[controller]/[action]")] [ApiController] public class UserController : ControllerBase { [HttpGet] [Authorize(UserPermission.UserCreate)] public ActionResult<string> UserCreate() => "UserCreate"; [HttpGet] [Authorize(UserPermission.UserUpdate)] public ActionResult<string> UserUpdate() => "UserUpdate"; [HttpGet] [Authorize(UserPermission.UserDelete)] public ActionResult<string> UserDelete() => "UserDelete"; }
Based on the above assumptions, the user access interface is as follows:
/api/User/UserCreate #success /api/User/UserUpdate #success /api/User/UserDelete #403 no permission
So far, Policy based authorization has been basically completed.
The next content will be the improvement or supplement of some of the above contents.
Perfect: implement policy provider PolicyProvider
Generally, the following authorization policies are added in the program CS, as follows:
builder.Services.AddAuthorization(options => { options.AddPolicy("policy", policy => policy.RequireClaim("EmployeeNumber")); });
Via authorizationoptions Addpolicy adding authorization policy is not flexible and cannot be added dynamically.
By implementing IAuthorizationPolicyProvider and adding it to DI, you can dynamically add policies.
The default implementation of IAuthorizationPolicyProvider is DefaultAuthorizationPolicyProvider.
Implement a PolicyProvider as follows:
public class TestAuthorizationPolicyProvider : DefaultAuthorizationPolicyProvider, IAuthorizationPolicyProvider { public Test2AuthorizationPolicyProvider(IOptions<AuthorizationOptions> options) : base(options) {} public new Task<AuthorizationPolicy> GetDefaultPolicyAsync() => return base.GetDefaultPolicyAsync(); public new Task<AuthorizationPolicy?> GetFallbackPolicyAsync() return base.GetFallbackPolicyAsync(); public new Task<AuthorizationPolicy?> GetPolicyAsync(string policyName) { if (policyName.StartsWith(UserPermission.User)) { var policy = new AuthorizationPolicyBuilder("Bearer"); policy.AddRequirements(new PermissionAuthorizationRequirement(policyName)); return Task.FromResult<AuthorizationPolicy?>(policy.Build()); } return base.GetPolicyAsync(policyName); } }
Note: the customized TestAuthorizationPolicyProvider must implement IAuthorizationPolicyProvider, otherwise it will not take effect when added to DI.
In program CS, add the custom PolicyProvider to DI:
builder.Services.AddSingleton<IAuthorizationPolicyProvider, TestAuthorizationPolicyProvider>();
Note: only the last added PolicyProvider will take effect.
Supplement: Custom AuthorizationMiddleware
Custom AuthorizationMiddleware can:
- Return customized response
- Enhance (or change) the default challenge or forbid response
Please refer to the official documents for details Customize the behavior of AuthorizationMiddleware
Supplement: authorization of MiniApi
In MiniApi, almost all of them are branch nodes in the shape of MapGet(). Such endpoints cannot use the [Authorize] label and can be authorized by RequireAuthorization("Something"), such as:
app.MapGet("/helloworld", () => "Hello World!") .RequireAuthorization("AtLeast21");
Reference source
ASP.NET Core 6.0 official documents: Authorization policy provider
. NET 6 steps to use JWT Bearer authentication and authorization
ASP.NET Core authentication and authorization 6: how is the authorization policy implemented? (mark: this one is very strong!)
ASP.NET Core authentication and authorization 7: dynamic authorization