“因此,角色不仅与用户相关联,还与他所操作的客户相关联”:
首先,您需要一个存储 Operator-Role-Customer 关系的表。您可以扩展内置的AspNetUserRoles 表,或者创建一个全新的表。由于我们不知道您的应用程序是什么样子,因此我将创建一个新表来说明如何执行此操作。假设我们有两个运算符:
- op1 既是客户 1 的管理员又是合作者,
- op2 是客户 2 的合作者:
operatorId | roleId | customerId
---------------+----------------+-----------------
op1 | Administrator | 1
---------------+----------------+-----------------
op1 | Collaborator | 2
---------------+----------------+-----------------
op2 | Collaborator | 2
然后我们可以定义一个CheckOperatorRoleForCustomer策略来动态检查操作员-角色-客户关系,而不是直接使用[Authorize(Roles="xxx")]:
services.AddAuthorization(opts =>{
opts.AddPolicy("CheckAdminForCustomer", pb =>{
pb.RequireAuthenticatedUser().AddRequirements(new OpRoleForCustomerRequirement("Administrator"));
});
opts.AddPolicy("CheckCollaboratorForCustomer", pb =>{
pb.RequireAuthenticatedUser().AddRequirements(new OpRoleForCustomerRequirement("Collaborator"));
});
});
services.AddHttpContextAccessor();
services.AddScoped<IAuthorizationHandler, OpRoleForCustomerHandler>();
这里的OpRoleForCustomerHandler 是一个AuthorizationHandler,用于检查当前用户是否具有指定客户所需的角色。
为方便起见,我在这里定义了两个策略:
-
CheckAdminForCustomer:检查当前用户是否具有指定客户的管理员角色
-
CheckCollaboratorForCustomer:检查当前用户是否具有指定客户的协作者角色
然后您可以通过以下方式应用该政策:
[Authorize(Policy="CheckAdminForCustomer")]
public IActionResult Profile(int CustomerId)
{
这种模式就是所谓的Policy-Based Authorization,比基于角色的授权更强大。
最后,这是我实现的OpRoleForCustomerHandler,供大家参考:
public class OpRoleForCustomerRequirement: IAuthorizationRequirement
{
public OpRoleForCustomerRequirement(string RoleId)
{
this.RoleId = RoleId;
}
public string RoleId{get;set;}
}
public class OpRoleForCustomerHandler: AuthorizationHandler<OpRoleForCustomerRequirement>
{
private readonly AppIdentityDbContext _dbContext;
private readonly UserManager<IdentityUser> _userManager;
private readonly IHttpContextAccessor _httpAccessor;
public OpRoleForCustomerHandler(AppIdentityDbContext dbContext, UserManager<IdentityUser> userManager, IHttpContextAccessor accessor)
{
this._dbContext = dbContext;
this._userManager = userManager;
this._httpAccessor = accessor;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, OpRoleForCustomerRequirement requirement)
{
if(context.User==null) { /* ...log... */ context.Fail(); return; }
var user = await this._userManager.GetUserAsync(context.User);
var httpContext = this._httpAccessor.HttpContext;
// get customId from HttpContext/RouteData/....
// for example ....
var customIdStr = httpContext.Request.Query["customerId"].FirstOrDefault();
if (!string.IsNullOrEmpty(customIdStr) ) {
var matches = this._dbContext.OpRoleForCustomers
.Any(opc =>
opc.OperatorId == user.Id
&& opc.RoleId == requirement.RoleId
&& opc.CustmerId == customIdStr
);
if(matches){ context.Succeed(requirement) ; return; }
}
context.Fail();
}
}
(您可能想要自定义从 HttpContext/RouteData/... 获取 customId 的方式)