私は、aspnetコアで許可ベースのアクセス制御を実装しようとしています。ユーザーのロールとパーミッション(create_product、delete_productなど)を動的に管理するために、それらはデータベースに保存されます。データモデルは http://i.stack.imgur.com/CHMPE.png のようなものです
Aspnetコア(MVC 5)の前に、以下のようなカスタムAuthorizeAttribute
を使用して問題を処理していました。
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
private readonly string _permissionName { get; set; }
[Inject]
public IAccessControlService _accessControlService { get; set; }
public CustomAuthorizeAttribute(string permissionName = "")
{
_permissionName = permissionName;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
var user = _accessControlService.GetUser();
if (PermissionName != "" && !user.HasPermission(_permissionName))
{
// set error result
filterContext.HttpContext.Response.StatusCode = 403;
return;
}
filterContext.HttpContext.Items["CUSTOM_USER"] = user;
}
}
それから私は以下のようなアクションメソッドでそれを使用していました:
[HttpGet]
[CustomAuthorize(PermissionEnum.PERSON_LIST)]
public ActionResult Index(PersonListQuery query){ }
さらに、ビューでHttpContext.Items ["CUSTOM_USER"]を使用して、HTMLパーツを表示または非表示にしました。
@if (CurrentUser.HasPermission("<Permission Name>"))
{
}
Aspnetコアを切り替えることにしたとき、私の計画はすべて失敗しました。 OnAuthorization
に仮想AuthorizeAttribute
メソッドがなかったため。問題を解決する方法をいくつか試しました。それらは以下のとおりです。
新しいポリシーベースの承認を使用する(私はそれが私のscenerioに適していないと思う)
カスタムAuthorizeAttribute
とAuthorizationFilter
を使用する(私はこの投稿を読む https://stackoverflow.com/a/35863514/54263 が、適切に変更できなかった)
カスタムミドルウェアの使用(現在のアクションのAuthorizeAttribute
を取得する方法?)
ActionFilterの使用(セキュリティの目的に合っていますか?)
自分のシーンに最適な方法とその実装方法を決定できませんでした。
最初の質問:MVC5の実装は悪い習慣ですか?
2番目の質問:aspnetコアを実装する提案はありますか?
コメントに基づいて、ポリシーベースの承認を使用する方法の例を次に示します。
public class PermissionRequirement : IAuthorizationRequirement
{
public PermissionRequirement(PermissionEnum permission)
{
Permission = permission;
}
public PermissionEnum Permission { get; }
}
public class PermissionHandler : AuthorizationHandler<PermissionRequirement>
{
private readonly IUserPermissionsRepository permissionRepository;
public PermissionHandler(IUserPermissionsRepository permissionRepository)
{
if(permissionRepository == null)
throw new ArgumentNullException(nameof(permissionRepository));
this.permissionRepository = permissionRepository;
}
protected override void Handle(AuthorizationContext context, PermissionRequirement requirement)
{
if(context.User == null)
{
// no user authorizedd. Alternatively call context.Fail() to ensure a failure
// as another handler for this requirement may succeed
return null;
}
bool hasPermission = permissionRepository.CheckPermissionForUser(context.User, requirement.Permission);
if (hasPermission)
{
context.Succeed(requirement);
}
}
}
Startup
クラスに登録します:
services.AddAuthorization(options =>
{
UserDbContext context = ...;
foreach(var permission in context.Permissions)
{
// assuming .Permission is enum
options.AddPolicy(permission.Permission.ToString(),
policy => policy.Requirements.Add(new PermissionRequirement(permission.Permission)));
}
});
// Register it as scope, because it uses Repository that probably uses dbcontext
services.AddScope<IAuthorizationHandler, PermissionHandler>();
そして最後にコントローラーで
[HttpGet]
[Authorize(Policy = PermissionEnum.PERSON_LIST.ToString())]
public ActionResult Index(PersonListQuery query)
{
...
}
このソリューションの利点は、要件に対して複数のハンドラーを使用できることです。つまり、最初のハンドラーが成功した場合、2番目のハンドラーはそれが失敗であると判断でき、ほとんど労力をかけずに リソースベースの承認 で使用できます。
ポリシーベースのアプローチは、ASP.NET Coreチームが推奨する方法です。
blowdart から:
カスタム認証属性を記述しないでください。あなたがそれをする必要があるなら、私たちは何か間違ったことをしました。代わりに、承認要件を作成する必要があります。
各権限にポリシーを追加する必要のないソリューションについては、別の質問については、my answer を参照してください。
これにより、コントローラーとアクションを任意のカスタム属性で装飾し、AuthorizationHandlerでそれらにアクセスできます。
私は同じ要件を持っていて、以下のようにそれをやったことがあり、それは私のためにうまくいきます。 .Net Core 2.0 Webapiを使用しています
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class CheckAccessAttribute : AuthorizeAttribute, IAuthorizationFilter
{
private string[] _permission;
public CheckAccessAttribute(params string[] permission)
{
_permission = permission;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
var user = context.HttpContext.User;
if (!user.Identity.IsAuthenticated)
{
return;
}
IRepository service = (IRepositoryWrapper)context.HttpContext.RequestServices.GetService(typeof(IRepository));
var success = service.CheckAccess(userName, _permission.ToList());
if (!success)
{
context.Result = JsonFormatter.GetErrorJsonObject(CommonResource.error_unauthorized,
StatusCodeEnum.Forbidden);
return;
}
return;
}
}
コントローラーでは次のように使用します
[HttpPost]
[CheckAccess(Permission.CreateGroup)]
public JsonResult POST([FromBody]Group group)
{
// your code api code here.
}