首页 > 解决方案 > 如何在应用程序中实现组的安全性

问题描述

我正在开发一个具有身份安全性的 API,该应用程序将具有组/页面(实体),并且每个实体将具有超级管理员、管理员和其他角色。对于整个应用程序角色来说,实施安全性是直接的,但我如何为组实施安全性?我有如下检查用户角色的代码

**存储库类**

public class UserEntityRoleRepository : RepositoryBase<UserEntityRole>, IUserEntityRoleRepository
    {
        public UserEntityRoleRepository(ARMASDataContext aRMASDataContext) : base(aRMASDataContext)
        {
        }

        public async Task<bool> isEntitySuperAdmin(string entityId, string userId) 
        {
            var entityRole = await this.ARMASDataContext.EntityRoles.FirstOrDefaultAsync(c => c.Name == "Entity Super Admin");

            return await this.hasRequiredRole(entityRole.Id, entityId, userId);
        }

        public async Task<bool> isEntityAdmin(string entityId, string userId)
        {
            var entityRole = await this.ARMASDataContext.EntityRoles.FirstOrDefaultAsync(c => c.Name == "Entity Admin");

            return await this.hasRequiredRole(entityRole.Id, entityId, userId);
        }

        public async Task<bool> isEntityHROfficer(string entityId, string userId)
        {
            var entityRole = await this.ARMASDataContext.EntityRoles.FirstOrDefaultAsync(c => c.Name == "Entity HR Officer");

            return await this.hasRequiredRole(entityRole.Id, entityId, userId);
        }

        public async Task<bool> isEntityAccountant(string entityId, string userId)
        {
            var entityRole = await this.ARMASDataContext.EntityRoles.FirstOrDefaultAsync(c => c.Name == "Entity Accounts");

            return await this.hasRequiredRole(entityRole.Id, entityId, userId);
        }

        public async Task<bool> isEntityAuditor(string entityId, string userId)
        {
            var entityRole = await this.ARMASDataContext.EntityRoles.FirstOrDefaultAsync(c => c.Name == "Entity Auditor");

            return await this.hasRequiredRole(entityRole.Id, entityId, userId);
        }

        public async Task<bool> isEntityConsultant(string entityId, string userId)
        {
            var entityRole = await this.ARMASDataContext.EntityRoles.FirstOrDefaultAsync(c => c.Name == "Entity Consultant");

            return await this.hasRequiredRole(entityRole.Id, entityId, userId);
        }

        private async Task<bool> hasRequiredRole(string entityRoleId, string entityId, string userId)
        {
            /*The following code is used to check if the provided EntityRoleId maps to row with User's 
            Id and Entity's Id  in UserEntity table through middle table UserEntityRole
            */

            return await this.ARMASDataContext.UserEntityRoles.Where(c => c.EntityRoleId == entityRoleId)
                                                                                .Include(c => c.UserEntity)
                                                                                .Where(c => c.UserEntity.UserId == userId && c.UserEntity.EntityId == entityId)
                                                                                .Select(c => new { c.Id })
                                                                                .AsQueryable().CountAsync() > 0;

        }
    }

**服务等级**

public class EntityService : IEntityService
    {
        .........

        public async Task<bool> TestEntityRoleCheckSuperAdmin(string entityId, string userId) 
        {
            return await _unitOfWork.UserEntityRole.isEntitySuperAdmin(entityId, userId);
        }

        public async Task<bool> TestEntityRoleCheckAdmin(string entityId, string userId)
        {
            return await _unitOfWork.UserEntityRole.isEntityAdmin(entityId, userId);
        }

        ........
    }

和控制器

public class EntityController : ControllerBase
    {
        ............

        // POST api/<EntityController>
        [Authorize]
        [HttpPost("RoleTestSuperAdmin")]
        public async Task<ActionResult<string>> RoleTestSuperAdmin()
        {
            string userId = User.FindFirstValue(ClaimTypes.NameIdentifier); // will give the user's userId
            string entityId = "e90fb3ab-347d-450f-ab9b-0cb187a22ae1";

           var hasRole = await _entityService.TestEntityRoleCheckSuperAdmin(entityId, userId);

            if (hasRole)
            {
                string message = "User Has the required role";
                
                var result = new OkObjectResult(new {message});
                return result;
            }

            return Unauthorized();
        }


        [Authorize]
        [HttpPost("RoleTestAdmin")]
        public async Task<IActionResult> RoleTestAdmin()
        {
            string userId = User.FindFirstValue(ClaimTypes.NameIdentifier); // will give the user's userId
            string entityId = "e90fb3ab-347d-450f-ab9b-0cb187a22ae1";

            var hasRole = await _entityService.TestEntityRoleAdmin(entityId, userId);

            if (hasRole)
            {
                return Ok(StatusCode(StatusCodes.Status200OK, new Response { Status = "Success", Message = "The account has The Requested Role" }));
            }

            return Unauthorized();

        }

       ........
    }

上面的代码工作得很好,但是有没有办法将它作为带有 UserId 和 EntityId 参数的策略来实现?

标签: c#asp.net-coreasp.net-identity

解决方案


您可以编写自定义策略处理程序并在应用程序中实现基于策略的授权。

例如:

创建一个 CustomUserEntityRoleClaim.cs 文件:它可以用来传输角色名称。

public class CustomUserEntityRoleClaim: IAuthorizationRequirement
{
    public string RoleName { get; }
    public CustomUserEntityRoleClaim(string roleName)
    {
        RoleName = roleName;
    }
}

创建一个 CustomUserEntityRoleHandler:在这个处理程序中,您可以调用您的 UserEntityRoleRepository 服务并检查用户的角色。

public class CustomUserEntityRoleHandler : AuthorizationHandler<CustomUserEntityRoleClaim>
{
    private readonly ApplicationDbContext _context;
    public CustomUserEntityRoleHandler(ApplicationDbContext context)
    {
        _context = context;
    }

    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CustomUserEntityRoleClaim requirement)
    {
        if (context.User == null || !context.User.Identity.IsAuthenticated)
        {
            context.Fail();
            return Task.CompletedTask;
        }
        //get current user id
        var userid = _context.ApplicationUsers.Where(c=>c.UserName ==  context.User.Identity.Name).FirstOrDefault().Id;
        //according to the role name to find the role id.
        var roleid = _context.EntityRoles.Where(c=>c.EntityRoleName== requirement.RoleName).Select(c=>c.EntityRoleId).FirstOrDefault();
        var validRole = false;

        //According to the userid and roleid to query the UserEntityRoles table, then based on the result to verify whether the user has the special roles.
        var result = _context.UserEntityRoles.Where(c => c.UserId == userid && c.EntityRoleId == roleid).ToList();

        if (result.Count>0)
        {
            validRole = true;
        } 

        if (validRole)
        {
            context.Succeed(requirement);
        }
        else
        {
            context.Fail();
        }
         
        return Task.CompletedTask;
    }
}

然后,在 Startup.ConfigureServices 方法中,注册 CustomUserEntityRoleHandler,并配置策略。

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(
                Configuration.GetConnectionString("DefaultConnection")));
        services.AddDatabaseDeveloperPageExceptionFilter();

        services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
            .AddEntityFrameworkStores<ApplicationDbContext>();

        services.ConfigureApplicationCookie(options =>
        {
            // Cookie settings
            options.Cookie.HttpOnly = true;
            options.ExpireTimeSpan = TimeSpan.FromMinutes(5);

            options.LoginPath = "/Identity/Account/Login";
            options.AccessDeniedPath = "/Login/UserAccessDenied"; // change the path to yours.
            options.SlidingExpiration = true;
        });

        //register the CustomUserEntityRoleHandler
        services.AddScoped<IAuthorizationHandler, CustomUserEntityRoleHandler>();
        //configure the policy.
        services.AddAuthorization(options =>
        {
            options.AddPolicy("AdminPolicy", policy =>
                policy.Requirements.Add(new CustomUserEntityRoleClaim("Admin")));
        }); 
        services.AddControllersWithViews();
    }

之后,在控制器中,您可以[Authorize]使用策略添加属性:

    [Authorize(Policy ="AdminPolicy")]
    public IActionResult Privacy()
    { 
        return View();
    }

截图如下:

在此处输入图像描述

有关创建基于策略的授权的更多详细信息,请查看以下文章:

ASP.NET Core 中基于策略的授权

ASP.NET Core 3.0 中基于策略和基于角色的授权使用自定义处理程序

[更新]

要为应用程序和实体添加多个角色,您可以尝试更改 CustomUserEntityRoleClaim 类,如下所示:

public class CustomUserEntityRoleClaim: IAuthorizationRequirement
{
    public string ApplicationRoleName { get; }
    public string EntityRoleName { get; set; }
    public CustomUserEntityRoleClaim(string approleName, string entityroleName)
    {
        ApplicationRoleName = approleName;
        EntityRoleName = entityroleName;
    }
}

然后,参考以下代码来配置策略:

        services.AddAuthorization(options =>
        {
            options.AddPolicy("AdminPolicy", policy =>
                policy.Requirements.Add(new CustomUserEntityRoleClaim("applicationRole", "entityrole")));
        });

并且,在CustomUserEntityRoleHandler的方法中,你可以访问ApplicationRoleNameproperty和EntityRoleNameproperty,然后查询数据库,检查用户是否有权限访问资源。


推荐阅读