如何扩展 ASP.NET Core 2.0 的角色以处理不同位置的不同权限?
How Can I Extend ASP.NET Core 2.0's Roles to Handle Different Permissions at Different Locations?
我们正在 Asp.net 核心 2.0 中实施一个新的 Web 应用程序,我希望能够根据事物的组合而不是一个特定的用户角色(如管理员、高级用户等)。问题 space 看起来像这样:
- 每个用户都可以拥有特定的 'home' 设施,他们根据自己的工作职能拥有默认权限。
- 每个 CRUD 操作都有与之关联的特定权限。
- 每个权限都可以在任意数量的设施上授予,一个,或者 none 全部(或它们的某种组合)。
- 特定用户在不同的设施可能拥有不同的权限。例如,区域经理可以在与他们合作的所有设施中拥有查看和订购权限,但仅对相邻区域的设施具有查看权限。
目前,我们使用的是一种无法控制权限的自制解决方案,但它仅适用于用户 'home' 设施。例如,我们不能授权从其他设施订购库存的人查看不同设施的库存。
我们试图为每个设施中的每个操作应用角色(哎呀!),这些都是动态生成的,但这会导致一些用户获得他们不应该拥有的权限。更不用说,维护它是一场噩梦。
如何扩展 ASP.NET Core 2.0 中的角色功能,让我的用户在不同的设施中拥有不同的权限,而不必为每个设施中的每个操作创建角色?
您可以创建一个 AuthorizationFilterAttribute 并将其分配给每个 API 端点或控制器 class。这将允许您为每个用户分配 case-by-case 权限,然后您只需要一个包含特定权限 ID 的 table。
这是一个从基本身份验证中提取用户名的实现。您可以通过更改 OnAuthorization 方法来更改对您的实现的身份验证,以从您存储的任何位置检索用户详细信息。
/// <summary>
/// Generic Basic Authentication filter that checks if user is allowed
/// to access a specific permssion.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class BasicAuthenticationFilter: AuthorizationFilterAttribute
{
private readonly bool _active = true;
private readonly int _permissionId;
public BasicAuthenticationFilter(int permissionId)
{
private _permissionId = permissionId;
}
/// <summary>
/// Overriden constructor to allow explicit disabling of this
/// filter's behavior. Pass false to disable (same as no filter
/// but declarative)
/// </summary>
public BasicAuthenticationFilter(bool active) => _active = active;
/// <summary>
/// Override to Web API filter method to handle Basic Authentication.
/// </summary>
public override void OnAuthorization(HttpActionContext actionContext)
{
if (!_active) return;
BasicAuthenticationIdentity identity = ParseAuthorizationHeader(actionContext);
if (identity == null && !OnAuthorizeUser(identity.Name, identity.Password, actionContext))
{
Challenge(actionContext);
return;
}
Thread.CurrentPrincipal = new GenericPrincipal(identity, null);
base.OnAuthorization(actionContext);
}
/// <summary>
/// Base implementation for user authentication you'll want to override this implementing
/// requirements on a case-by-case basis.
/// </summary>
protected virtual bool OnAuthorizeUser(string username, string password, HttpActionContext actionContext)
{
if (!Authorizer.Validate(username,password)) // check if user is authentic
return false;
using (var db = new DbContext())
{
var userPermissions = _context.UserPermissions
.Where(user => user.UserName == username);
if (userPermissions.Permission.Contains(_permissionId))
return true;
else
return false;
return true;
}
}
/// <summary>
/// Parses the Authorization header and creates user credentials
/// </summary>
protected virtual BasicAuthenticationIdentity ParseAuthorizationHeader(HttpActionContext actionContext)
{
string authHeader = null;
System.Net.Http.Headers.AuthenticationHeaderValue auth = actionContext.Request.Headers.Authorization;
if (auth?.Scheme == "Basic")
authHeader = auth.Parameter;
if (String.IsNullOrEmpty(authHeader))
return null;
authHeader = Encoding.Default.GetString(Convert.FromBase64String(authHeader));
string[] tokens = authHeader.Split(':');
if (tokens.Length < 2)
return null;
return new BasicAuthenticationIdentity(tokens[0], tokens[1]);
}
/// <summary>
/// Send the Authentication Challenge request
/// </summary>
private void Challenge(HttpActionContext actionContext)
{
var host = actionContext.Request.RequestUri.DnsSafeHost;
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
actionContext.Response.Headers.Add("WWW-Authenticate", String.Format("Basic realm=\"{0}\"", host));
}
}
然后您只需将过滤器添加到您的 API 方法中:
private const int _orderPermission = 450;
/// <summary>
/// Submit a new order.
/// </summary>
[HttpPost, BasicAuthenticationFilter(_orderPermission)]
public void Submit([FromBody]OrderData value)
{
Task.Run(()=> ProcessInbound());
return Ok();
}
我建议使用政策。他们给了你很大的 finer-grained 控制权。基本上,您从一个或多个 "requirements" 开始。例如,您可以从以下内容开始:
public class ViewFacilitiesRequirement : IAuthorizationRequirement
{
}
public class OrderFacilitiesRequirement : IAuthorizationRequirement
{
}
这些主要用作授权处理程序的附件,因此它们非常基本。内容来自那些授权处理程序,您可以在其中定义满足要求的实际含义。
public class ViewFacilitiesHandler : AuthorizationHandler<ViewFacilitiesRequirement>
{
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, ViewFacilitiesRequirement requirement)
{
// logic here, if user is authorized:
context.Succeed(requirement);
}
}
授权处理程序是依赖注入的,因此您可以以正常方式将 DbContext
、UserManager<TUser>
等内容注入其中,然后查询这些来源以确定用户是否已授权。
获得一些要求和处理程序后,您需要注册它们:
services.AddAuthorization(o =>
{
o.AddPolicy("ViewFacilities", p =>
p.Requirements.Add(new ViewFacilitiesRequirement()));
});
services.AddScoped<IAuthorizationHandler, ViewFacilitiesHandler>();
如果不是很明显,一个策略可以利用多个要求。所有这些都必须通过才能使政策通过。处理程序只需要在 DI 容器中注册。它们会根据适用的要求类型自动应用。
然后,在需要此权限的控制器或操作上:
[Authorize(Policy = "ViewFacilities")]
当然,这是一个非常基本的示例。您可以制作可以处理多种不同需求的处理程序。您可以进一步构建您的要求,因此您也不需要那么多。或者您可能更喜欢更明确,并为每个特定场景设置 requirements/handlers。这完全取决于你。
有关详细信息,请参阅 documentation。
我们正在 Asp.net 核心 2.0 中实施一个新的 Web 应用程序,我希望能够根据事物的组合而不是一个特定的用户角色(如管理员、高级用户等)。问题 space 看起来像这样:
- 每个用户都可以拥有特定的 'home' 设施,他们根据自己的工作职能拥有默认权限。
- 每个 CRUD 操作都有与之关联的特定权限。
- 每个权限都可以在任意数量的设施上授予,一个,或者 none 全部(或它们的某种组合)。
- 特定用户在不同的设施可能拥有不同的权限。例如,区域经理可以在与他们合作的所有设施中拥有查看和订购权限,但仅对相邻区域的设施具有查看权限。
目前,我们使用的是一种无法控制权限的自制解决方案,但它仅适用于用户 'home' 设施。例如,我们不能授权从其他设施订购库存的人查看不同设施的库存。
我们试图为每个设施中的每个操作应用角色(哎呀!),这些都是动态生成的,但这会导致一些用户获得他们不应该拥有的权限。更不用说,维护它是一场噩梦。
如何扩展 ASP.NET Core 2.0 中的角色功能,让我的用户在不同的设施中拥有不同的权限,而不必为每个设施中的每个操作创建角色?
您可以创建一个 AuthorizationFilterAttribute 并将其分配给每个 API 端点或控制器 class。这将允许您为每个用户分配 case-by-case 权限,然后您只需要一个包含特定权限 ID 的 table。
这是一个从基本身份验证中提取用户名的实现。您可以通过更改 OnAuthorization 方法来更改对您的实现的身份验证,以从您存储的任何位置检索用户详细信息。
/// <summary>
/// Generic Basic Authentication filter that checks if user is allowed
/// to access a specific permssion.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class BasicAuthenticationFilter: AuthorizationFilterAttribute
{
private readonly bool _active = true;
private readonly int _permissionId;
public BasicAuthenticationFilter(int permissionId)
{
private _permissionId = permissionId;
}
/// <summary>
/// Overriden constructor to allow explicit disabling of this
/// filter's behavior. Pass false to disable (same as no filter
/// but declarative)
/// </summary>
public BasicAuthenticationFilter(bool active) => _active = active;
/// <summary>
/// Override to Web API filter method to handle Basic Authentication.
/// </summary>
public override void OnAuthorization(HttpActionContext actionContext)
{
if (!_active) return;
BasicAuthenticationIdentity identity = ParseAuthorizationHeader(actionContext);
if (identity == null && !OnAuthorizeUser(identity.Name, identity.Password, actionContext))
{
Challenge(actionContext);
return;
}
Thread.CurrentPrincipal = new GenericPrincipal(identity, null);
base.OnAuthorization(actionContext);
}
/// <summary>
/// Base implementation for user authentication you'll want to override this implementing
/// requirements on a case-by-case basis.
/// </summary>
protected virtual bool OnAuthorizeUser(string username, string password, HttpActionContext actionContext)
{
if (!Authorizer.Validate(username,password)) // check if user is authentic
return false;
using (var db = new DbContext())
{
var userPermissions = _context.UserPermissions
.Where(user => user.UserName == username);
if (userPermissions.Permission.Contains(_permissionId))
return true;
else
return false;
return true;
}
}
/// <summary>
/// Parses the Authorization header and creates user credentials
/// </summary>
protected virtual BasicAuthenticationIdentity ParseAuthorizationHeader(HttpActionContext actionContext)
{
string authHeader = null;
System.Net.Http.Headers.AuthenticationHeaderValue auth = actionContext.Request.Headers.Authorization;
if (auth?.Scheme == "Basic")
authHeader = auth.Parameter;
if (String.IsNullOrEmpty(authHeader))
return null;
authHeader = Encoding.Default.GetString(Convert.FromBase64String(authHeader));
string[] tokens = authHeader.Split(':');
if (tokens.Length < 2)
return null;
return new BasicAuthenticationIdentity(tokens[0], tokens[1]);
}
/// <summary>
/// Send the Authentication Challenge request
/// </summary>
private void Challenge(HttpActionContext actionContext)
{
var host = actionContext.Request.RequestUri.DnsSafeHost;
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
actionContext.Response.Headers.Add("WWW-Authenticate", String.Format("Basic realm=\"{0}\"", host));
}
}
然后您只需将过滤器添加到您的 API 方法中:
private const int _orderPermission = 450;
/// <summary>
/// Submit a new order.
/// </summary>
[HttpPost, BasicAuthenticationFilter(_orderPermission)]
public void Submit([FromBody]OrderData value)
{
Task.Run(()=> ProcessInbound());
return Ok();
}
我建议使用政策。他们给了你很大的 finer-grained 控制权。基本上,您从一个或多个 "requirements" 开始。例如,您可以从以下内容开始:
public class ViewFacilitiesRequirement : IAuthorizationRequirement
{
}
public class OrderFacilitiesRequirement : IAuthorizationRequirement
{
}
这些主要用作授权处理程序的附件,因此它们非常基本。内容来自那些授权处理程序,您可以在其中定义满足要求的实际含义。
public class ViewFacilitiesHandler : AuthorizationHandler<ViewFacilitiesRequirement>
{
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, ViewFacilitiesRequirement requirement)
{
// logic here, if user is authorized:
context.Succeed(requirement);
}
}
授权处理程序是依赖注入的,因此您可以以正常方式将 DbContext
、UserManager<TUser>
等内容注入其中,然后查询这些来源以确定用户是否已授权。
获得一些要求和处理程序后,您需要注册它们:
services.AddAuthorization(o =>
{
o.AddPolicy("ViewFacilities", p =>
p.Requirements.Add(new ViewFacilitiesRequirement()));
});
services.AddScoped<IAuthorizationHandler, ViewFacilitiesHandler>();
如果不是很明显,一个策略可以利用多个要求。所有这些都必须通过才能使政策通过。处理程序只需要在 DI 容器中注册。它们会根据适用的要求类型自动应用。
然后,在需要此权限的控制器或操作上:
[Authorize(Policy = "ViewFacilities")]
当然,这是一个非常基本的示例。您可以制作可以处理多种不同需求的处理程序。您可以进一步构建您的要求,因此您也不需要那么多。或者您可能更喜欢更明确,并为每个特定场景设置 requirements/handlers。这完全取决于你。
有关详细信息,请参阅 documentation。