我们有一个asp.net mvc应用程序,我正在移植到aspnet核心mvc。 在旧解决方案中,使用Windows身份验证进行身份验证。
最重要的是,我们有一个“基于活动的身份验证”(如http://ryankirkman.com/2013/01/31/activity-based-authorization.html);用户连接到角色,角色连接到权限。用户角色和相应的权限存储在一个单独的应用程序中,该应用程序用作我们的应用程序和其他一些系统的授权服务。
向授权服务api查询用户“Jon Doe”的权限会得到如下响应:
{
Email:"Jon.Doe@acme.com",
FirstName:"Jon",
LastName:"Doe",
Resources:
[
"CanAccessWebApplication",
"CanCopyAppointment",
"CanEditAppointment",
"CanEditContact",
"CanSaveContact"
...
]
Alias:"1234567",
UserId:"1234"
}
在我们当前的应用程序中,使用控制器方法的属性(我们已经实现了自己)来检查这些权限:
public ContactController
{
[ActionUserAccess("CanSaveContact")]
public ActionResult SaveContact
{
...
}
}
ActionUserAccessAttribute
过滤器的当前遗留实现如下所示:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public sealed class ActionUserAccessAttribute : ActionFilterAttribute
{
private readonly string _accessRight;
public ActionUserAccessAttribute(string accessRight)
{
_accessRight = accessRight;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
throw new InvalidOperationException("ActionUserAccessAttribute can not be used for controllers or actions configured for anonymous access");
}
base.OnActionExecuting(filterContext);
var securityService = ContainerResolver.Container.GetInstance<ISecurityService>();
var hasResource = securityService.HasAccess(_accessRight);
if (!hasResource)
{
filterContext.Result =
new HttpStatusCodeResult(
403,
string.Format(
"User {0} is not authorized to access the resource:'{1}' ",
filterContext.HttpContext.User.Identity.Name,
_accessRight));
}
}
}
}
将属性/过滤器移植到aspnetcore似乎非常简单,但根据“asp.net安全人员”@blowdart的回答https://stackoverflow.com/a/31465227/1257728,我们不应该这样做。
如果没有将自定义过滤器移植到aspnetcore,最适合在这里实现的是什么?
也许我们可以使用基于角色的身份验证https://docs.microsoft.com/en-us/aspnet/core/security/authorization/roles?
我们可以创建一个中间件来填充授权服务api中的用户访问权限并压缩权限并将其作为ClaimTypes.Role
添加到用户ClaimsIdentity
?然后我们将使用上面的方法,如:
[Authorize(Roles = "CanSaveContact")]
public ActionResult Save()
这种方法的错误在于,这不是关于角色,而是关于访问权限的更多信息。
我还查看了基于策略的授权:
https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies
在控制器中看起来像这样:
[Authorize(Policy = "CanSaveContact")]
public ActionResult Save()
但是当我阅读上面基于微软政策的示例中的代码时,我必须将安全服务api中存在的所有可用访问权限添加为ConfigureService
类的Startup
方法中的策略能够使用它们。我觉得好像很尴尬(伪代码):
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
IEnumerable<string> allAccessRights = _securtiyService.GetAllAccessRights();
services.AddAuthorization(options =>
{
foreach(var accessRight in allAccessRights)
{
options.AddPolicy(accessRight, policy => policy.Requirements.Add(new AccessRightRequirement(accessRight));
}
});
services.AddSingleton<IAuthorizationHandler, AccessRightHandler>();
}
然后AccessRightHandler
可以使用以验证用户的访问权限。编写AccessRightHandler是可以的,但似乎没有必要将所有权限添加为策略。
在我们的aspnetcore应用程序中实现此类授权的最佳方法是什么?
答案 0 :(得分:8)
很好的问题,我认为很多人在升级到ASP.NET Core时会遇到同样的问题。
Barry Dorrans(@blowdart)是绝对正确的,你不应该编写自己的自定义授权属性 - ASP.NET Core中的授权已经大大改进,你绝对可以根据自己的需要进行创建。
这当然很大程度上取决于您当前的应用程序,以及您拥有的角色,因此我将根据您在上面提供的代码段做出一些假设。
在开始之前,我真的建议您阅读新的Authorization docs for ASP.NET Core,以及Barry Dorran在GitHub上的Authorization workshop。我强烈建议你通过后者,他也有一个.NET Core 2.0分支。
根据您的实施方式,您可以使用基于声明的授权,也可以使用基于资源的。
看看你的角色,看起来基于资源的auth在你的情况下实际上可以很好用!
例如:
确定可能的操作(从您的资源中获取操作Name
):
public static class Operations
{
public static OperationAuthorizationRequirement Access = new OperationAuthorizationRequirement { Name = "Access" };
public static OperationAuthorizationRequirement Copy = new OperationAuthorizationRequirement { Name = "Copy" };
public static OperationAuthorizationRequirement Edit = new OperationAuthorizationRequirement { Name = "Edit" };
public static OperationAuthorizationRequirement Save = new OperationAuthorizationRequirement { Name = "Save" };
public static OperationAuthorizationRequirement Delete = new OperationAuthorizationRequirement { Name = "Delete" };
}
创建基本资源授权处理程序:
public abstract class BaseResourceAuthorizationHandler<TResource> : AuthorizationHandler<OperationAuthorizationRequirement, TResource>
{
private readonly string _resourceType;
public BaseResourceAuthorizationHandler(string resourceType)
{
_resourceType = resourceType;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, OperationAuthorizationRequirement requirement, TResource resource)
{
if (context.User.HasClaim("Resources", $"Can{requirement.Name}{_resourceType}"))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
实施特定的基于资源的处理程序。资源是应用程序中绑定对象到资源中的实体。此类将成为当前资源角色,Operations和ASP.NET Core中的授权系统之间的粘合剂。这些也可以扩展为为任何特定资源类型/操作添加额外的逻辑 例如,对于约会:
public class AppointmentAuthorizationHandler : BaseResourceAuthorizationHandler<Appointment>
{
public AppointmentAuthorizationHandler() : base("Appointment") { }
}
然后注册:
services.AddSingleton<IAuthorizationHandler, AppointmentAuthorizationHandler>();
然后在你的控制器中:
public class AppointmentsController : Controller
{
IAppointmentsRepository _appointmentsRepository;
IAuthorizationService _authorizationService;
public AppointmentsController(IAppointmentsRepository appointmentsRepository,
IAuthorizationService authorizationService)
{
_appointmentsRepository = appointmentsRepository;
_authorizationService = authorizationService;
}
public IActionResult Edit(int id)
{
var appointment = _appointmentsRepository.Get(id);
if (appointment == null)
{
return new NotFoundResult();
}
if (!(await _authorizationService.AuthorizeAsync(User, appointment, Operations.Edit)))
{
return new ChallengeResult();
}
return View(appointment);
}
}
您也可以在视图中执行相同操作,以检查是否允许用户查看“编辑”按钮,例如:
@using Microsoft.AspNetCore.Authorization
@model IEnumerable<Appointment>
@inject IAuthorizationService AuthorizationService
<h1>Document Library</h1>
@foreach (var appointment in Model)
{
if (await AuthorizationService.AuthorizeAsync(User, appointment, Operations.Edit))
{
<p>@Html.ActionLink("Appointment #" + appointment.Id, "Edit", new { id = appointment.Id })</p>
}
}
P.S。只是要添加一个注释 - 是的,你失去了按属性过滤的能力,但最后这种方式更好。首先 - 您远离基于字符串的角色,您根据操作类型和资源类型请求权限。其次,您可以以更好(和智能的方式)处理权限,以及组合多个权限检查。
它看起来更复杂,但它也更强大:)
答案 1 :(得分:1)
在这里扮演魔鬼的拥护者,并建议替代我的other answer - 这可能是一个基于@ mortb请求的简单选项,并且可能适合从当前系统迁移的一些人。
根据你的情况,基于策略的auth真的不适合你的用例 - 它是一个更强大的选项,除了检查是否存在Resource
字符串之外你还没有真正使用它来自您的API。
另一方面,我不会放弃角色方法。您从外部API获得的资源列表不是严格的资源,但同时它完全映射到您的需求。在一天结束时,您要做的就是检查用户是否对特定请求具有一个(或多个)资源访问权限。
就像您在帖子中提到的那样,您必须扩展您的授权以从外部API填充角色。不要忘记,您的ClaimsIdentity
具有RoleClaimType
属性,该属性标记用于存储角色的声明的类型。它通常设置为ClaimTypes.Role
,但并非总是如此。
您甚至可以继续,并创建自定义身份验证属性,与此不同:
public class AuthorizeAccessAttribute : AuthorizeAttribute
{
public AuthorizeAccessAttribute(string entity)
{
Roles = "CanAccess" + entity;
}
}
public class AuthorizeEditAttribute : AuthorizeAttribute
{
public AuthorizeEditAttribute(string entity)
{
Roles = "CanEdit" + entity;
}
}
所以你可以按如下方式使用它:
[AuthorizeEdit("Appointment")]
public IActionResult Edit(int id)
{
return View();
}