How to implement role authorization with custom database?

試著忘記壹切 提交于 2021-02-11 17:44:49

问题


I have an application which requires role authorization using custom database. The database is set up with a tblUsers table that has a reference to a tblRoles table. The users are also already assigned to their roles.

I also want to use the [Authorize(Role = "RoleName")] attribute on each action to check if an authenticated user is assigned to "RoleName" in the database. I'm having a lot of trouble figuring out where I need to make a modification to the [Authorize] attribute so it behaves that way. I just want to see if a username has a role, I won't have a page to manage roles in the database.

I have tried implementing custom storage providers for ASP.NET Core Identity, but it's starting to look like this is not what I need because I'm not gonna be managing roles within the application, and I can't tell how it affects the behavior of [Authorize] attribute.

Also, it's likely that I have a false assumption in my understanding on how the [Authorize] attribute even works. If you notice it, I would appreciate if you could point it out.


回答1:


I had a similar problem when my client asked for granular permissions for each role. I couldn't find a way to modify the Authorize attribute but was able to implement the solution with a custom attribute. But it depends on one thing i.e can you get the userId of the calling user? I used cookie authentication so I just include the userId in my claims when someone logs in so when a request comes I can always get it from there. I think the built-in Session logic in asp.net might get the job done too, I can't say for sure though. Anyways the logic for custom authorization goes like this:

  1. Load users and roles from database to cache on startup. If you haven't set up a cache in your program (and don't want to) you can simply make your own for this purpose by making a UserRoleCache class with 2 static lists in it. Also there are several ways of loading data from db on startup but I found it easy to do that directly in Program.cs as you'll see below.
  2. Define your custom attribute to check if the calling user has the required role by iterating over lists in cache and return 403 if not.

Modify your Program class like:

    public class Program
    {
        public static async Task Main(string[] args)
        {
            IWebHost webHost = CreateWebHostBuilder(args).Build();

            using (var scope = webHost.Services.CreateScope())
            {
                //Get the DbContext instance. Replace MyDbContext with the 
                //actual name of the context in your program
                var context = scope.ServiceProvider.GetRequiredService<MyDbContext>();

                List<User> users = await context.User.ToListAsync();
                List<Role> roles = await context.Role.ToListAsync();

                //You may make getters and setters, this is just to give you an idea
                UserRoleCache.users = users;
                UserRoleCache.roles = roles;

            }

            webHost.Run();
        }

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>();

    }

Then comes the logic for checking if user has a role. Notice I've used an array of roles because sometimes you'll want to allow access to multiple roles.

    public class RoleRequirementFilter : IAuthorizationFilter
    {
        private readonly string[] _roles;

        public PermissionRequirementFilter(string[] roles)
        {
            _roles = roles;
        }

        public void OnAuthorization(AuthorizationFilterContext context)
        {
            bool hasRole = false;

            //Assuming there's a way you can get the userId
            var userId = GetUserId();

            User user = UserRoleCache.users.FirstOrDefault(x => x.Id == userId);
            //Where roleType is the name of the role like Admin, Manager etc
            List<Role> roles = UserRoleCache.roles.FindAll(x => _roles.Contains(x.RoleType))

            foreach(var role in roles)
            {
                if(user.RoleId == role.Id)
                {
                    hasRole = true;
                    break;
                }
            }

            if (!hasRole)
                context.Result = new StatusCodeResult(403);
        }
    }

Finally make the Role attribute

    public class RoleAttribute : TypeFilterAttribute
    {
        public RoleAttribute(params string[] roles) : base(typeof(RoleRequirementFilter))
        {
            Arguments = new object[] { roles };
        }
    }

Now you can use the Role attribute in your controllers:

public class SampleController : ControllerBase
    {

        [HttpGet]
        [Role("Admin", "Manager")]
        public async Task<ActionResult> Get()
        {

        }

        [HttpPost]
        [Role("Admin")]
        public async Task<ActionResult> Post()
        {

        }
    }


来源:https://stackoverflow.com/questions/60289878/how-to-implement-role-authorization-with-custom-database

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!