web-dev-qa-db-ja.com

ASP.NET CoreでカスタムのAuthorizeAttributeをどのように作成しますか?

私は、ASP.NET Coreでカスタム認証属性を作成しようとしています。以前のバージョンではbool AuthorizeCore(HttpContextBase httpContext)をオーバーライドすることが可能でした。しかし、これは AuthorizeAttribute にはもう存在しません。

カスタムAuthorizeAttributeを作成するための現在のアプローチは何ですか?

達成しようとしていること:Header AuthorizationでセッションIDを受信して​​います。そのIDから、特定のアクションが有効かどうかがわかります。

293
jltrem

ASP.Netコアチームによって推奨されるアプローチは、完全に文書化されている ここ という新しいポリシー設計を使用することです。新しいアプローチの背後にある基本的な考え方は、新しい[Authorize]属性を使用して「ポリシー」を指定することです(たとえば、ポリシーがアプリケーションのStartup.csに登録されている場合は[Authorize( Policy = "YouNeedToBe18ToDoThis")])。年齢が18歳以上であると主張する)。

ポリシー設計はフレームワークへのすばらしい追加であり、ASP.Netセキュリティコアチームはその導入について称賛されるべきです。それは言った、それはすべてのケースには適していません。このアプローチの欠点は、特定のコントローラーまたはアクションに特定のクレームタイプが必要であると単純に主張するという最も一般的なニーズに対して便利な解決策を提供できないことです。アプリケーションが個々のRESTリソースに対するCRUD操作を管理する何百もの個別の許可( "CanCreateOrder"、 "CanReadOrder"、 "CanUpdateOrder"、 "CanDeleteOrder"など)を持つ場合、新しいアプローチのどちらかポリシー名とクレーム名の間に1対1の繰り返しマッピング(例:options.AddPolicy("CanUpdateOrder", policy => policy.RequireClaim(MyClaimTypes.Permission, "CanUpdateOrder));)、または実行時にこれらの登録を実行するためのコードの作成(例:データベースからすべてのクレームタイプを読み込み、ループで前述の呼び出しを実行) 。ほとんどの場合、このアプローチの問題は、不要なオーバーヘッドがあることです。

ASP.Netコアセキュリティチームは独自のソリューションを作成しないことをお勧めしますが、場合によってはこれを開始するための最も慎重な選択肢となる可能性があります。

以下は、IAuthorizationFilterを使用して、特定のコントローラまたはアクションのクレーム要件を表現する簡単な方法を提供する実装です。

public class ClaimRequirementAttribute : TypeFilterAttribute
{
    public ClaimRequirementAttribute(string claimType, string claimValue) : base(typeof(ClaimRequirementFilter))
    {
        Arguments = new object[] {new Claim(claimType, claimValue) };
    }
}

public class ClaimRequirementFilter : IAuthorizationFilter
{
    readonly Claim _claim;

    public ClaimRequirementFilter(Claim claim)
    {
        _claim = claim;
    }

    public void OnAuthorization(AuthorizationFilterContext context)
    {
        var hasClaim = context.HttpContext.User.Claims.Any(c => c.Type == _claim.Type && c.Value == _claim.Value);
        if (!hasClaim)
        {
            context.Result = new ForbidResult();
        }
    }
}


[Route("api/resource")]
public class MyController : Controller
{
    [ClaimRequirement(MyClaimTypes.Permission, "CanReadResource")]
    [HttpGet]
    public IActionResult GetResource()
    {
        return Ok();
    }
}
295
Derek Greer

私はasp.netのセキュリティ担当者です。 まず、ミュージックストアのサンプルや単体テストの範囲外で、まだ文書化されていないことをお詫び申し上げます。公開されているAPIに関しては、まだすべて洗練されています。 詳細なドキュメントは ここ です。

カスタムの承認属性を作成してほしくありません。あなたがそれをする必要があるならば、我々は何か間違ったことをしました。代わりにあなたはauthorization requirements と書くべきです。

承認はIDに基づいて機能します。 IDは認証によって作成されます。

あなたはコメントであなたがヘッダーのセッションIDをチェックしたいと言います。あなたのセッションIDはアイデンティティの基礎になるでしょう。 Authorize属性を使いたい場合は、そのヘッダを受け取ってそれを認証済みのClaimsPrincipalに変換するための認証ミドルウェアを作成します。あなたはそれから認可要件の中でそれをチェックするでしょう。認証要件は、あなたが好きなだけ複雑になることがあります。例えば、現在の身元について生年月日を請求し、ユーザーが18歳以上であれば認証します。

public class Over18Requirement : AuthorizationHandler<Over18Requirement>, IAuthorizationRequirement
{
        public override void Handle(AuthorizationHandlerContext context, Over18Requirement requirement)
        {
            if (!context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth))
            {
                context.Fail();
                return;
            }

            var dateOfBirth = Convert.ToDateTime(context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth).Value);
            int age = DateTime.Today.Year - dateOfBirth.Year;
            if (dateOfBirth > DateTime.Today.AddYears(-age))
            {
                age--;
            }

            if (age >= 18)
            {
                context.Succeed(requirement);
            }
            else
            {
                context.Fail();
            }
        }
    }
}

それであなたのConfigureServices()関数では、あなたはそれを配線するでしょう

services.AddAuthorization(options =>
{
    options.AddPolicy("Over18", 
        policy => policy.Requirements.Add(new Authorization.Over18Requirement()));
});

そして最後にそれをコントローラやアクションメソッドに適用します。

[Authorize(Policy = "Over18")]
220
blowdart

ASP.NET Core 2では、AuthorizeAttributeを継承することができます。IAuthorizationFilter(またはIAsyncAuthorizationFilter)も実装する必要があります。

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class CustomAuthorizeAttribute : AuthorizeAttribute, IAuthorizationFilter
{
    private readonly string _someFilterParameter;

    public CustomAuthorizeAttribute(string someFilterParameter)
    {
        _someFilterParameter = someFilterParameter;
    }

    public void OnAuthorization(AuthorizationFilterContext context)
    {
        var user = context.HttpContext.User;

        if (!user.Identity.IsAuthenticated)
        {
            // it isn't needed to set unauthorized result 
            // as the base class already requires the user to be authenticated
            // this also makes redirect to a login page work properly
            // context.Result = new UnauthorizedResult();
            return;
        }

        // you can also use registered services
        var someService = context.HttpContext.RequestServices.GetService<ISomeService>();

        var isAuthorized = someService.IsUserAuthorized(user.Identity.Name, _someFilterParameter);
        if (!isAuthorized)
        {
            context.Result = new StatusCodeResult((int)System.Net.HttpStatusCode.Forbidden);
            return;
        }
    }
}
58
gius

カスタムAuthorizeAttributeを作成するための現在のアプローチは何ですか?

簡単です:あなた自身のAuthorizeAttributeを作成しないでください。

純粋な認証シナリオ(特定のユーザーのみにアクセスを制限する場合など)では、新しい認証ブロックを使用することをお勧めします。 https://github.com/aspnet/MusicStore/blob/1c0aeb08bb1ebd846726232226279bbe001782e1/samples/MusicStore/Startup.cs #L84-L92

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.Configure<AuthorizationOptions>(options =>
        {
            options.AddPolicy("ManageStore", policy => policy.RequireClaim("Action", "ManageStore"));
        });
    }
}

public class StoreController : Controller
{
    [Authorize(Policy = "ManageStore"), HttpGet]
    public async Task<IActionResult> Manage() { ... }
}

認証に関しては、それはミドルウェアレベルで最もよく処理されます。

あなたは正確に何を達成しようとしていますか?

23
Pinpoint

あなたはあなたのControllersとActionsのカスタム属性を見つけてHandleRequirementAsyncメソッドに渡すあなた自身のAuthorizationHandlerを作ることができます。

public abstract class AttributeAuthorizationHandler<TRequirement, TAttribute> : AuthorizationHandler<TRequirement> where TRequirement : IAuthorizationRequirement where TAttribute : Attribute
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, TRequirement requirement)
    {
        var attributes = new List<TAttribute>();

        var action = (context.Resource as AuthorizationFilterContext)?.ActionDescriptor as ControllerActionDescriptor;
        if (action != null)
        {
            attributes.AddRange(GetAttributes(action.ControllerTypeInfo.UnderlyingSystemType));
            attributes.AddRange(GetAttributes(action.MethodInfo));
        }

        return HandleRequirementAsync(context, requirement, attributes);
    }

    protected abstract Task HandleRequirementAsync(AuthorizationHandlerContext context, TRequirement requirement, IEnumerable<TAttribute> attributes);

    private static IEnumerable<TAttribute> GetAttributes(MemberInfo memberInfo)
    {
        return memberInfo.GetCustomAttributes(typeof(TAttribute), false).Cast<TAttribute>();
    }
}

それからあなたはあなたのコントローラーやアクションに必要なカスタム属性のためにそれを使うことができます。例えば、許可要件を追加するために。カスタム属性を作成するだけです。

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class PermissionAttribute : AuthorizeAttribute
{
    public string Name { get; }

    public PermissionAttribute(string name) : base("Permission")
    {
        Name = name;
    }
}

次に、あなたのポリシーに追加する要件を作成します。

public class PermissionAuthorizationRequirement : IAuthorizationRequirement
{
    //Add any custom requirement properties if you have them
}

次に、先ほど作成したAttributeAuthorizationHandlerを継承して、カスタム属性のAuthorizationHandlerを作成します。これは、HandleRequirementsAsyncメソッド内のすべてのカスタム属性に対してIEnumerableを渡され、ControllerおよびActionから累積されます。

public class PermissionAuthorizationHandler : AttributeAuthorizationHandler<PermissionAuthorizationRequirement, PermissionAttribute>
{
    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionAuthorizationRequirement requirement, IEnumerable<PermissionAttribute> attributes)
    {
        foreach (var permissionAttribute in attributes)
        {
            if (!await AuthorizeAsync(context.User, permissionAttribute.Name))
            {
                return;
            }
        }

        context.Succeed(requirement);
    }

    private Task<bool> AuthorizeAsync(ClaimsPrincipal user, string permission)
    {
        //Implement your custom user permission logic here
    }
}

そして最後に、Startup.csのConfigureServicesメソッドで、カスタムのAuthorizationHandlerをサービスに追加して、ポリシーを追加します。

        services.AddSingleton<IAuthorizationHandler, PermissionAuthorizationHandler>();

        services.AddAuthorization(options =>
        {
            options.AddPolicy("Permission", policyBuilder =>
            {
                policyBuilder.Requirements.Add(new PermissionAuthorizationRequirement());
            });
        });

今、あなたはあなたのカスタム属性であなたのコントローラーとアクションを単に装飾することができます。

[Permission("AccessCustomers")]
public class CustomersController
{
    [Permission("AddCustomer")]
    IActionResult AddCustomer([FromBody] Customer customer)
    {
        //Add customer
    }
}
22
Shawn

Derek Greer _ great _ answerに基づいて、私は列挙型でそれをしました。

これが私のコードの例です。

public enum PermissionItem
{
    User,
    Product,
    Contact,
    Review,
    Client
}

public enum PermissionAction
{
    Read,
    Create,
}


public class AuthorizeAttribute : TypeFilterAttribute
{
    public AuthorizeAttribute(PermissionItem item, PermissionAction action)
    : base(typeof(AuthorizeActionFilter))
    {
        Arguments = new object[] { item, action };
    }
}

public class AuthorizeActionFilter : IAuthorizationFilter
{
    private readonly PermissionItem _item;
    private readonly PermissionAction _action;
    public AuthorizeActionFilter(PermissionItem item, PermissionAction action)
    {
        _item = item;
        _action = action;
    }
    public void OnAuthorization(AuthorizationFilterContext context)
    {
        bool isAuthorized = MumboJumboFunction(context.HttpContext.User, _item, _action); // :)

        if (!isAuthorized)
        {
            context.Result = new ForbidResult();
        }
    }
}

public class UserController : BaseController
{
    private readonly DbContext _context;

    public UserController( DbContext context) :
        base()
    {
        _logger = logger;
    }

    [Authorize(PermissionItem.User, PermissionAction.Read)]
    public async Task<IActionResult> Index()
    {
        return View(await _context.User.ToListAsync());
    }
}
21
bruno.almeida