ASP. Net core framework exploration

Time:2022-5-13

Today, let’s explore ASP Net core framework. We know that when the request enters the pipeline processing process, we will first use authentication for user authentication, and then use authorization for user authorization. If you haven’t seen the certification process, you can go toAuthentication

AddAuthorization

First of all, we need to use authorization services in the pipeline in the same way. We first need to add relevant services to the container, and then use useauthorization in pipeline processing. Some people may wonder why there is only useauthorization in the project automatically generated by the framework and no code like addauthorization?

1 private static IMvcCoreBuilder AddControllersCore(IServiceCollection services)
 2 {
 3     return services.AddMvcCore().AddAuthorization();
 4 }
 5 
 6 public static IServiceCollection AddAuthorizationCore(this IServiceCollection services)
 7 {
 8     if (services == null)
 9     {
10         throw new ArgumentNullException(nameof(services));
11     }
12 
13     services.TryAdd(ServiceDescriptor.Transient());
14     services.TryAdd(ServiceDescriptor.Transient());
15     services.TryAdd(ServiceDescriptor.Transient());
16     services.TryAdd(ServiceDescriptor.Transient());
17     services.TryAdd(ServiceDescriptor.Transient());
18     services.TryAddEnumerable(ServiceDescriptor.Transient());
19     return services;
20 }

Through the source code, we can see that this process is actually done when adding MVC services, and add authorization is to inject some necessary services in the user authorization process into the container.

Let’s take a look at the handler useauthorization added in the pipeline to see how the framework authorizes users.

 

UseAuthorization

In pipeline processing, the logic of authorization process is defined in the authorization middleware. When the user requests a resource, when the processing process comes to the pipeline, the middleware must first check whether the resource needs authorization. If the authorizeattribute is used for marking, it indicates that authorization is required. Therefore, the first step is to get the user to add it

The information in the authorizeattribute, and the information related to the controller and action in the MVC process will be saved in the metadata of the obtained endpoint, so the first step is to obtain the information of iauthorizedata from the metadata.

Let’s first learn the concepts of several key objects in authorization:

IAuthorizeData

We know that if authorization verification needs to be enabled for a requested resource, we need to add the authorize feature to a controller or action. For example, if the role name needs to be administrator, we usually add [authorize (roles = “admin”)].

Let’s take a look at the source code of the authorizeattribute feature:

 1 public class AuthorizeAttribute : Attribute, IAuthorizeData
 2 {
 3     ...
 4 
 5     public string Policy { get; set; }
 6 
 7     public string Roles { get; set; }
 8 
 9     public string AuthenticationSchemes { get; set; }
10 }

As you can see, the feature inherits from the iauthorizedata interface, which defines three attributes:

Policy: used to define the name of the policy on which authorization is based

Roles: used to define the role name on which authorization is based

Authenticationschemes: used to define the user authentication scheme used before adopting this authorization method

These three attributes are the attributes defined in the iauthorizedata interface. When the pipeline is matched to the appropriate endpoint through the processing of userouting middleware, the iauthorizedata information added on the request resource will be added to the metadata of the endpoint.

 

Iauthorizationrequirement and authorizationhandler

Iauthorizationrequirement interface is an empty interface, which is of no practical use. It is only used for marking. Its implementation class is an authorization rule.

AuthorizationHandlerIt is an abstract base class used to define authorization processing rules. It inherits from the iauthorizationhandler interface, which has only one handleasync interface. The authorization rules required by the resources requested by the system are defined by overriding handleasync by inheriting this abstract base class. Developers can define authorization rules according to specific scenarios.

Let’s take the role authorization rule rolesauthorizationrequirement of the system. We want to authorize based on rolename, so we inherit the authorization handlerAnd iauthorizationrequirement. In authorization processing, whether the authorization is successful is returned by judging whether the user information contains the specified role name. The following is the source code of rolesauthorizationrequirement:

1 public class RolesAuthorizationRequirement : AuthorizationHandler, IAuthorizationRequirement
 2 {
 3     public RolesAuthorizationRequirement(IEnumerable allowedRoles)
 4     {
 5         if (allowedRoles == null)
 6         {
 7             throw new ArgumentNullException(nameof(allowedRoles));
 8         }
 9 
10         if (allowedRoles.Count() == 0)
11         {
12             throw new InvalidOperationException(Resources.Exception_RoleRequirementEmpty);
13         }
14         AllowedRoles = allowedRoles;
15     }
16     
17     public IEnumerable AllowedRoles { get; }
18 
19     protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RolesAuthorizationRequirement requirement)
20     {
21         if (context.User != null)
22         {
23             bool found = false;
24             if (requirement.AllowedRoles == null || !requirement.AllowedRoles.Any())
25             {
26                 // Review: What do we want to do here?  No roles requested is auto success?
27             }
28             else
29             {
30                 found = requirement.AllowedRoles.Any(r => context.User.IsInRole(r));
31             }
32             if (found)
33             {
34                 context.Succeed(requirement);
35             }
36         }
37         return Task.CompletedTask;
38     }
39 
40 }

In the actual business scenario, the authorization rules involved may be various. It is possible to want the user’s gender to be a girl, it is possible to require the user to be at least 18 years old, and it may be a complex situation with multiple conditions. Therefore, we can follow the role authorization rules to customize the requirement class and inherit this abstract class and interface.

 

AuthorizationPolicy

In the process of authorization, there may be more than one authorization rule for a resource, but multiple authorization rules need to be met. Therefore, we need to have a set of authorization rules that can indicate the needs of a request. This is the role of authorization policy. Let’s look at the source code first:

ASP. Net core framework explorationASP. Net core framework exploration

1 public class AuthorizationPolicy
 2 {
 3     /// 
 4     /// Creates a new instance of .
 5     /// 
 6     /// 
 7     /// The list of s which must succeed for
 8     /// this policy to be successful.
 9     /// 
10     /// 
11     /// The authentication schemes the  are evaluated against.
12     /// 
13     public AuthorizationPolicy(IEnumerable requirements, IEnumerable authenticationSchemes)
14     {
15         if (requirements == null)
16         {
17             throw new ArgumentNullException(nameof(requirements));
18         }
19 
20         if (authenticationSchemes == null)
21         {
22             throw new ArgumentNullException(nameof(authenticationSchemes));
23         }
24 
25         if (requirements.Count() == 0)
26         {
27             throw new InvalidOperationException(Resources.Exception_AuthorizationPolicyEmpty);
28         }
29         Requirements = new List(requirements).AsReadOnly();
30         AuthenticationSchemes = new List(authenticationSchemes).AsReadOnly();
31     }
32 
33 
34     public IReadOnlyList Requirements { get; }
35 
36     public IReadOnlyList AuthenticationSchemes { get; }
37 
38     public static async Task CombineAsync(IAuthorizationPolicyProvider policyProvider, IEnumerable authorizeData)
39     {
40         ...
41 
42         AuthorizationPolicyBuilder policyBuilder = null;
43 
44         foreach (var authorizeDatum in authorizeData)
45         {
46             if (policyBuilder == null)
47             {
48                 policyBuilder = new AuthorizationPolicyBuilder();
49             }
50 
51             var useDefaultPolicy = true;
52             if (!string.IsNullOrWhiteSpace(authorizeDatum.Policy))
53             {
54                 var policy = await policyProvider.GetPolicyAsync(authorizeDatum.Policy);
55  
56                 useDefaultPolicy = false;
57             }
58 
59             var rolesSplit = authorizeDatum.Roles?.Split(',');
60             if (rolesSplit != null && rolesSplit.Any())
61             {
62                 var trimmedRolesSplit = rolesSplit.Where(r => !string.IsNullOrWhiteSpace(r)).Select(r => r.Trim());
63                 policyBuilder.RequireRole(trimmedRolesSplit);
64                 useDefaultPolicy = false;
65             }
66 
67             var authTypesSplit = authorizeDatum.AuthenticationSchemes?.Split(',');
68             if (authTypesSplit != null && authTypesSplit.Any())
69             {
70                 foreach (var authType in authTypesSplit)
71                 {
72                     if (!string.IsNullOrWhiteSpace(authType))
73                     {
74                         policyBuilder.AuthenticationSchemes.Add(authType.Trim());
75                     }
76                 }
77             }
78 
79             if (useDefaultPolicy)
80             {
81                 policyBuilder.Combine(await policyProvider.GetDefaultPolicyAsync());
82             }
83         }
84         ...
85 
86         return policyBuilder?.Build();
87     }
88 }

View Code

There are two collection attributes defined in the class: requirements and authenticationschemes, which are used to store the authorization rule iauthorizationrequirement and the authentication scheme name respectively.

In the combinesync method, this method will accept the incoming IEnumerableSet. After a loop, the three attributes in each iauthorizedata are converted:

Query the iauthorizationpolicyprovider through the policy name to return the authorizationpolicy, and the collection properties in the object will be added to the authorizationpolicybuilder;

Use rolesauthorizationrequirement constructor in authorizationpolicybuilder through roles to generate rolesauthorizationrequirement object and add it to the requirements collection, while authenticationschemes is the authenticationschemes collection added to the authorizationpolicybuilder object

Finally, by using the build method of the authorizationpolicybuilder object, the requirements and authenticationschemes in the object are used as the parameters of the authorizationpolicy constructor to generate the authorizationpolicy object. It can be seen that combinesync is the completion of IEnumerableConvert the attributes in to generate a complete authorization policy for the requested resource.

Through the authorization policy, the iauthorizedata interface is smoothly connected with iauthorization requirement and authorization handlerIt can also be said that the authorization requirements added by the user in the authorizeattribute are transformed into specific authorizationrequirement objects, and the authorization can be completed by executing the processing logic in the objects.

 

In fact, after introducing the above three concepts, the general logic of our authorization is basically clear:

1. Obtain iauthorizedata in metadata through the endpoint that requests resource matching;

2. Query and transform the three attributes in iauthorizedata of resource settings into an overall authorization policy;

3. Use the authenticationschemes in the authorizationpolicy to complete the authentication of user information;

4. Use the requirements in the authorization policy to complete user authorization

 

doubt

The above introduces several very important concepts in authorization and the general basic logic of authorization, but after careful consideration, there are still some doubts in the above process:

1. We know that using [authorize (roles = “admin”)] role-based authorization will eventually generate a rolesauthorizationrequirement object according to the name. How can we authorize based on policy?

If you need to use policy for authorization, we generally need to define the policyname and policy content when adding the authorization service to the container, as shown in the following code:

1 services.AddAuthorization(option =>
2 {
3     option.AddPolicy("CustomPolicy", authorizationPolicyBuilder =>
4     {
5         authorizationPolicyBuilder
6             .RequireRole("admin")
7             .RequireClaim(ClaimTypes.Email);
8     });
9 });

Then add [authorize (policy = “custompolicy”)] to the resources to be controlled.

2. After setting the authorizationoptions through the delegate parameter of addauthorization, how can we get it when necessary?

Authorizationoptions contains a dictionary collection idictionary used to store the relationship between policyname and policy content, addpolicy is responsible for adding the corresponding relationship to the dictionary, saving the parameter configuration of authorizationoptions in the container through the configure option mode, and obtaining the object information through ioptions when needed. Using policyname, you can obtain the corresponding authorizationpolicy from the dictionary collection. This is also explained in the authorization policy In the combinesync method, when we obtain the authorizationpolicy in the iauthorizationpolicyprovider through policyname, how does this object in the iauthorizationpolicyprovider come from? In fact, the implementation of iauthorizationpolicyprovider obtains the authorizationoptions object through the option mode.

3. Before entering the user authorization phase, the system has used the default authentication scheme for user authentication. Why does authenticationschemes still exist in the authorization attribute? That is, what is the use of finally converting to the authenticationschemes collection in the authorizationpolicy?

When adding a user authentication service to the container, we generally need to specify the default authentication scheme, and our authentication service can often add multiple authentication schemes. In some scenarios, we need to limit the requested resources. When the user authorizes and uses a non default scheme for authentication, the authenticationschemes in the feature plays a role, Developers can flexibly specify the authenticationschemes attribute to limit the authentication scheme of resources, and then authorize.

 

Write at the end

That’s all for this sharing about authorization. I’ve considered how to share this time for a long time. Finally, after sorting out the main points, this way may be clearer. I hope you can gain something. If you have any questions, you can leave a message in the comment area for discussion. New blogger, if you like, please give us some praise. You are also very welcome to put forward your valuable opinions!!!