ASP.Net Identity built in functions with custom tables in ASP.Net Core

后端 未结 1 1046
栀梦
栀梦 2020-12-21 18:05

I am using ASP.Net Core Web Api 2 on .Net 2.1 Framework I have custom AppUsers and AppRoles tables, linked with bridge table AppUserRoles

My main problem is that I w

相关标签:
1条回答
  • 2020-12-21 18:34

    You don't need to override Authorize or IsInRole. Just add the roles as claim to the User.Identity. You can use middleware to do the claims transformation.

    As an example I suggest you take a look at the PolicyServer. It has the same approach. The free OSS version adds claims in the middleware.

    /// Add the policy server claims transformation middleware to the pipeline.
    /// This middleware will turn application roles and permissions into claims
    /// and add them to the current user
    public static IApplicationBuilder UsePolicyServerClaims(this IApplicationBuilder app)
    {
        return app.UseMiddleware<PolicyServerClaimsMiddleware>();
    }
    

    Where PolicyServerClaimsMiddleware is:

    public class PolicyServerClaimsMiddleware
    {
        private readonly RequestDelegate _next;
    
        /// <summary>
        /// Initializes a new instance of the <see cref="PolicyServerClaimsMiddleware"/> class.
        /// </summary>
        /// <param name="next">The next.</param>
        public PolicyServerClaimsMiddleware(RequestDelegate next)
        {
            _next = next;
        }
    
        /// <summary>
        /// Invoke
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="client">The client.</param>
        /// <returns></returns>
        public async Task Invoke(HttpContext context, IPolicyServerRuntimeClient client)
        {
            if (context.User.Identity.IsAuthenticated)
            {
                var policy = await client.EvaluateAsync(context.User);
    
                var roleClaims = policy.Roles.Select(x => new Claim("role", x));
                var permissionClaims = policy.Permissions.Select(x => new Claim("permission", x));
    
                var id = new ClaimsIdentity("PolicyServerMiddleware", "name", "role");
                id.AddClaims(roleClaims);
                id.AddClaims(permissionClaims);
    
                context.User.AddIdentity(id);
            }
            await _next(context);
        }
    }
    

    And from startup:

    public void ConfigureServices(IServiceCollection services)
    {
    
        services.AddMvcCore(options =>
        {
            // workaround: https://github.com/aspnet/Mvc/issues/7809
            options.AllowCombiningAuthorizeFilters = false;
        })
        .SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
        .AddAuthorization();
    
        // This is not relevant for you, but just to show how policyserver is implemented.
        // The bottom line is that you can implement this anyway you like.
    
        // this sets up the PolicyServer client library and policy
        // provider - configuration is loaded from appsettings.json
        services.AddPolicyServerClient(Configuration.GetSection("Policy"))
            .AddAuthorizationPermissionPolicies();
    
    }
    
    public void Configure(IApplicationBuilder app)
    {
        app.UseAuthentication();
    
        // add this middleware to make roles and permissions available as claims
        // this is mainly useful for using the classic [Authorize(Roles="foo")] and IsInRole functionality
        // this is not needed if you use the client library directly or the new policy-based authorization framework in ASP.NET Core
        app.UsePolicyServerClaims();
    
        app.UseMvc();
    }
    

    The example reads the configuration from file, which may be an option for you as well. But you can also implement a store and add some caching.

    If you want to add some authorization logic then I suggest you create some policies and authorization handlers. Just make sure that you use the middleware at the right place.


    An alternative is to use your own filter / attribute:

    //using Microsoft.AspNetCore.Authorization;
    //using Microsoft.AspNetCore.Mvc;
    //using Microsoft.AspNetCore.Mvc.Filters;
    
    public class CustomPolicyAttribute : AuthorizeAttribute, IAsyncAuthorizationFilter
    {
        private int _number;
    
        public CustomPolicyAttribute(int number)
        {
            _number = number;
        }
    
        public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
        {
            var service = (IAuthorizationService)context.HttpContext.RequestServices.GetService(typeof(IAuthorizationService));
    
            var requirement = new CustomRequirement
            {
                Number = _number
            };
            var result = await service.AuthorizeAsync(context.HttpContext.User, null, requirement);
            if (!result.Succeeded)
                context.Result = new ForbidResult();
        }
    }
    

    You can use this in a couple of ways. Use as attribute (the Authorize equivalent):

    [CustomPolicy(1)]
    public async Task<IActionResult> DoSomething()
    {
    
    }
    

    Or validate manually (the IsInRole equivalent):

    public class MyController : Controller
    {
        private readonly IAuthorizationService _authorizationService;
    
        public MyController(IAuthorizationService authorizationService)
        {
            _authorizationService = authorizationService;
        }
    
        public async Task<IActionResult> DoSomething(int number)
        {
            var requirement = new CustomRequirement
            {
                Number = number
            };
            var result = await _authorizationService.AuthorizeAsync(User, null, requirement);
            if (!result.Succeeded) return Forbid();
    
            return View("success");
        }
    }
    

    You will need an AuthorizationHandler to evaluate the requirement:

    public class CustomRequirementHandler : AuthorizationHandler<CustomRequirement>
    {
        // Use dependency injection to include services you need.
        public CustomRequirementHandler ()
        {
        }
    
        protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, CustomRequirement requirement)
        {
            // Add your advanced check here.
            if (requirement.Number > 0)
            {
                context.Succeed(requirement);
            }
        }
    }
    

    And register that in the startup:

    services.AddTransient<IAuthorizationHandler, CustomRequirementHandler>();
    

    In the handler you can add your own logic. In that case you won't have to add policies and you don't have to add authorization as claims.

    0 讨论(0)
提交回复
热议问题