我们正在Asp.net core 2.0中实现一个新的Web应用程序,我希望能够基于一组事物来限制操作,而不是一个特定的用户角色(如管理员,高级用户等)。问题空间如下所示:
目前,我们使用的是本土解决方案,这种解决方案已失控以限制权限,但它仅适用于用户的家庭设施。我们无法授予从其他工厂订购库存的人员,例如,查看其他工厂的库存。
我们试图仅为每个设施中的每个操作应用角色(Yikes!),这些角色是动态生成的,但这会导致某些用户获得他们不应拥有的权限。更不用说,这是一个维持的噩梦。
如何在ASP.NET Core 2.0中扩展角色功能,以允许我的用户在不同的设施中拥有不同的权限,而无需为每个设施的每个操作创建角色?
我建议使用政策。它们为您提供更精细的控制。基本上,您从一个或多个“要求”开始。例如,您可以从以下内容开始:
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")]
当然,这是一个非常基本的例子。您可以制作处理程序,而不是处理多种不同的要求。您可以更多地构建您的需求,因此您也不需要那么多。或者您可能更喜欢更明确,并且针对每个特定方案都有需求/处理程序。这完全取决于你。
有关更多详细信息,请参阅documentation。
您可以创建AuthorizationFilterAttribute并将其分配给每个API端点或Controller类。这将允许您为每个用户分配逐个案例的权限,然后您只需要一个包含特定权限ID的表。
这是一个从基本身份验证中提取用户名的实现。您可以通过更改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();
}