"Таким образом, роль связана не только с пользователем, но и с клиентом, для которого он работает" * :
Во-первых, вам нужна таблица который хранит отношения Оператор-Роль-Клиент. Вы можете расширить встроенную таблицу AspNetUserRoles
или создать новую таблицу. Поскольку мы не знаем, как выглядит ваше приложение, я создам новую таблицу, чтобы проиллюстрировать, как это сделать. Допустим, у нас есть два оператора:
- op1 является одновременно администратором и соавтором для клиента 1,
- , а op2 является соавтором для клиента 2:
operatorId | roleId | customerId
---------------+----------------+-----------------
op1 | Administrator | 1
---------------+----------------+-----------------
op1 | Collaborator | 2
---------------+----------------+-----------------
op2 | Collaborator | 2
и затем вместо непосредственного использования [Authorize(Roles="xxx")]
мы можем определить политику CheckOperatorRoleForCustomer , которая динамически проверяет отношения оператор-роль-клиент:
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 : проверить, имеет ли текущий пользователь роль Collaborator для указанного клиента
И затем вы можете применить политику:
[Authorize(Policy="CheckAdminForCustomer")]
public IActionResult Profile(int CustomerId)
{
This это так называемая авторизация на основе политик , которая более мощная, чем авторизация на основе ролей.
Наконец, вот моя реализация 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();
}
}
(Возможно, вы захотите настроить способ получения customId из HttpContext / RouteData /...)