web-dev-qa-db-ja.com

ASP.NET Core 1.0 MVCでグローバル認証フィルターをオーバーライドする

ASP.NET Core 1.0(MVC 6)Webアプリで承認を設定しようとしています。

より制限的なアプローチ-デフォルトでは、すべてのコントローラーとアクションメソッドをAdminロールを持つユーザーに制限します。そのため、次のようなグローバル認証属性を追加しています。

AuthorizationPolicy requireAdminRole = new AuthorizationPolicyBuilder()
    .RequireAuthenticatedUser()
    .RequireRole("Admin")
    .Build();
services.AddMvc(options => { options.Filters.Add(new AuthorizeFilter(requireAdminRole));});

次に、特定の役割を持つユーザーが具体的なコントローラーにアクセスできるようにします。例えば:

[Authorize(Roles="Admin,UserManager")]
public class UserControler : Controller{}

「グローバルフィルター」はUserManagerが「管理者」ではないのでコントローラーにアクセスすることを許可しないため、もちろん機能しません。

MVC5では、カスタム認証属性を作成し、そこにロジックを配置することでこれを実装できました。次に、このカスタム属性をグローバルとして使用します。例えば:

public class IsAdminOrAuthorizeAttribute : AuthorizeAttribute
{
    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        ActionDescriptor action = filterContext.ActionDescriptor;
        if (action.IsDefined(typeof(AuthorizeAttribute), true) ||
            action.ControllerDescriptor.IsDefined(typeof(AuthorizeAttribute), true))
        {
            return;
        }

        base.OnAuthorization(filterContext);
    }
}

カスタムAuthorizeFilterを作成しようとしましたが、成功しませんでした。 APIは異なるようです。

だから私の質問は次のとおりです。デフォルトのポリシーを設定してから、特定のコントローラーとアクションに対してそれをオーバーライドすることは可能ですか?または似たようなもの。これで行きたくない

[Authorize(Roles="Admin,[OtherRoles]")]

これは潜在的なセキュリティ問題であるため、すべてのコントローラー/アクションで。誤ってAdminロールを配置するのを忘れるとどうなりますか。

25
regnauld

グローバルポリシーは、特定のコントローラーとアクションに適用するポリシーよりも制限が厳しいため、フレームワークを少し使用する必要があります。

  • デフォルトではAdminユーザーのみがアプリケーションにアクセスできます
  • 特定のロールには、一部のコントローラーへのアクセスも許可されます(UserManagersUsersControllerにアクセスするなど)

既にお知らせしたように、グローバルフィルターを使用すると、Adminユーザーのみがコントローラーにアクセスできるようになります。 UsersControllerに追加の属性を追加すると、bothAdminandUserManagerがアクセスできます。

MVC 5のアプローチと同様のアプローチを使用することもできますが、動作は異なります。

  • MVC 6では、 [Authorize] 属性に認証ロジックが含まれていません。
  • 代わりに、 AuthorizeFilter は、ポリシーが満たされていることを確認するために許可サービスを呼び出すOnAuthorizeAsyncメソッドを持つものです。
  • 特定の IApplicationModelProvider を使用して、[Authorize]属性を持つすべてのコントローラーとアクションにAuthorizeFilterを追加します。

1つのオプションはIsAdminOrAuthorizeAttributeを再作成することですが、今回はAuthorizeFilterとして作成し、グローバルフィルタとして追加します。

public class IsAdminOrAuthorizeFilter : AuthorizeFilter
{
    public IsAdminOrAuthorizeFilter(AuthorizationPolicy policy): base(policy)
    {
    }

    public override Task OnAuthorizationAsync(Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext context)
    {
        // If there is another authorize filter, do nothing
        if (context.Filters.Any(item => item is IAsyncAuthorizationFilter && item != this))
        {
            return Task.FromResult(0);
        }

        //Otherwise apply this policy
        return base.OnAuthorizationAsync(context);
    }        
}

services.AddMvc(opts => 
{
    opts.Filters.Add(new IsAdminOrAuthorizeFilter(new AuthorizationPolicyBuilder().RequireRole("admin").Build()));
});

これは、コントローラー/アクションに特定の[Authorize]属性がない場合にのみ、グローバルフィルターを適用します。


また、すべてのコントローラーとアクションに適用されるフィルターを生成するプロセスに自分自身を挿入することにより、グローバルフィルターを使用しないようにすることもできます。独自のIApplicationModelProviderまたは独自のIApplicationModelConventionを追加できます。両方とも、特定のコントローラーおよびアクションフィルターを追加/削除できます。

たとえば、デフォルトの許可ポリシーと追加の特定のポリシーを定義できます。

services.AddAuthorization(opts =>
{
    opts.DefaultPolicy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().RequireRole("admin").Build();
    opts.AddPolicy("Users", policy => policy.RequireAuthenticatedUser().RequireRole("admin", "users"));
});

次に、新しいIApplicatioModelProviderを作成して、独自の[Authorize]属性を持たないすべてのコントローラーにデフォルトポリシーを追加します(アプリケーションの規則は非常によく似ており、おそらくフレームワークは拡張されることを意図しています。私はすぐに既存のAuthorizationApplicationModelProviderをガイドとして使用しました):

public class OverridableDefaultAuthorizationApplicationModelProvider : IApplicationModelProvider
{
    private readonly AuthorizationOptions _authorizationOptions;

    public OverridableDefaultAuthorizationApplicationModelProvider(IOptions<AuthorizationOptions> authorizationOptionsAccessor)
    {
        _authorizationOptions = authorizationOptionsAccessor.Value;
    }

    public int Order
    {
        //It will be executed after AuthorizationApplicationModelProvider, which has order -990
        get { return 0; }
    }

    public void OnProvidersExecuted(ApplicationModelProviderContext context)
    {
        foreach (var controllerModel in context.Result.Controllers)
        {
            if (controllerModel.Filters.OfType<IAsyncAuthorizationFilter>().FirstOrDefault() == null)
            {
                //default policy only used when there is no authorize filter in the controller
                controllerModel.Filters.Add(new AuthorizeFilter(_authorizationOptions.DefaultPolicy));
            }
        }
    }

    public void OnProvidersExecuting(ApplicationModelProviderContext context)
    {            
        //empty    
    }
}

//Register in Startup.ConfigureServices
services.TryAddEnumerable(
    ServiceDescriptor.Transient<IApplicationModelProvider, OverridableDefaultAuthorizationApplicationModelProvider>());

これを設定すると、これらの2つのコントローラーでデフォルトポリシーが使用されます。

public class FooController : Controller

[Authorize]
public class BarController : Controller

ここでは、特定のユーザーポリシーが使用されます。

[Authorize(Policy = "Users")]
public class UsersController : Controller

管理者ロールをすべてのポリシーに追加する必要がありますが、少なくともすべてのポリシーが単一の起動方法で宣言されることに注意してください。おそらく管理者ロールを常に追加するポリシーを作成する独自のメソッドを作成できます。

35
Daniel J.G.

@Danielのソリューションを使用して、コメントで@TarkaDaalが言及した同じ問題に遭遇しました(各呼び出しのコンテキストには2 AuthorizeFilterがあります...それらがどこから来たのかはよくわかりません)。

だからそれを解決する私の方法は次のとおりです:

_public class IsAdminOrAuthorizeFilter : AuthorizeFilter
{
    public IsAdminOrAuthorizeFilter(AuthorizationPolicy policy): base(policy)
    {
    }

    public override Task OnAuthorizationAsync(Microsoft.AspNet.Mvc.Filters.AuthorizationContext context)
    {
        if (context.Filters.Any(f =>
        {
            var filter = f as AuthorizeFilter;
            //There's 2 default Authorize filter in the context for some reason...so we need to filter out the empty ones
            return filter?.AuthorizeData != null && filter.AuthorizeData.Any() && f != this;
        }))
        {
            return Task.FromResult(0);
        }

        //Otherwise apply this policy
        return base.OnAuthorizationAsync(context);
    }        
}

services.AddMvc(opts => 
{
    opts.Filters.Add(new IsAdminOrAuthorizeFilter(new AuthorizationPolicyBuilder().RequireRole("admin").Build()));
});
_

これはいですが、引数なしでAuthorize属性のみを使用している場合は、とにかくnew AuthorizationPolicyBuilder().RequireRole("admin").Build()フィルターによって処理されるため、このケースでは機能します。

1
cheesemacfly