web-dev-qa-db-ja.com

ASP MVCで承認チェックを実装する正しい方法

ASP MVCには、コントローラーレベルまたはコントローラーメソッドレベルでチェックを実行するためのAuthorize属性があります。しかし、コントローラーメソッド内でアクセス許可を確認する必要がある場合はどうでしょうか。たとえば、ブログ投稿を送信するための更新または作成アクションを実行するとします。適切な権限を持つ一部のユーザーは、ファイルを添付したり、ブログ投稿を固定したりできます。新しい投稿を作成するときは、モデルを保存する前にこれらすべての追加チェックを行う必要があります。 Laravelには、ユーザーが関連するアクションを実行する能力を持っているかどうかを確認するためにコントローラーメソッド内でチェックを行うことができる機能の概念があります。同様に、ビュー内のそれらの能力を使用して、表示または非表示にするアイテムをチェックできます-これはすべてボックスから出てきます。

ASP MVCに似たものはありますか?コントローラーメソッド内でチェックアクセス許可をどのように実装しますか?次のようなプロパティを持つアクセス許可クラスを作成しますか?

public class Permissions
{
    private readonly IPrincipal user;

    public Permissions (IPrincipal user)
    {
        this.user = user;
    }

    public bool CanUploadFiles
    {
        get { return user.IsInAnyRole("Standard", "Admin"); }
    }

    public bool CanDeleteItems
    {
        get { return user.IsInRole("Admin"); }
    }

     public bool CanLockPost
    {
        get { return user.IsInRole("Admin"); }
    }

    // other permissions
}

次に、コントローラのアクション内:

 public ActionResult Create(PostViewModel viewModel)
 {
       var permissions = new Permissions(User);

        if (ModelState.IsValid)
        {
                var post = new Post
                {
                   if (permissions.CanLockPost)
                    {
                        post.IsLocked = viewModel.IsLocked;
                    }
                    if (permissions.CanStickyPost)
                    {
                        post.IsSticky = viewModel.IsSticky;
                    }
                    // Set other properties
                }

               _postRepository.Add(post);
        }   
  }

または、データベースに権限を保存しますか。コントローラーまたはコントローラーのアクションレベルではなく、より細かいレベルでチェックを実装する方法についてのご意見をお聞かせください。例示するいくつかのコード例が役に立ちます。

7
adam78

IPrincipalインターフェースの拡張メソッドを使用してこれを実装しています。

public static class PermissionExtenions
{
    public bool CanUploadFiles(this IPrincipal user)
    {
        return user.IsInAnyRole("Standard", "Admin");
    }

    public bool CanDeleteItems(this IPrincipal user)
    {
        return user.IsInRole("Admin");
    }
}

これに関する素晴らしい点は、IPrincipalをラップする新しいオブジェクトを作成する必要がないことです。コントローラーの上部にusingディレクティブを追加し、それを名前空間のViews/Web.configファイルに追加すると、Userプロパティにアクセスして、コントローラーとビューでこれを使用できます。

これを独自のカスタム認証属性で使用することもできます。

public void OnAuthorization(AuthorizationContext filterContext)
{
    var controller = filterContext.Controller as Controller;

    if (controller == null)
        return;

    var user = controller.User;

    if (!user.CanUploadFiles())
    {
        // redirect to some page
    }
1
Greg Burghardt

これを見てください: https://docs.asp.net/en/latest/security/authorization/roles.html

「これらのロールがどのように作成および管理されるかは、承認プロセスのバッキングストアによって異なります。ロールは、ClaimsPrincipalクラスのIsInRoleプロパティを通じて開発者に公開されます。」

したがって、プリンシパルのIsInRole()を使用してロールを確認している限り、あなたは正しい方向に進んでいると思います。

その他のランダムな考え-ビューモデルに固有のものである場合に、権限チェックを使いやすくするために、ビューモデルクラスでそれらを公開することができます。そうすることで、かみそりビューはビューモデルをチェックして、ユーザーに何を表示すべきか、何を表示すべきでないかを確認できます。

また、同じアクセス許可チェックを使用して、ビューモデルクラスにIValidatableObjectを実装し、Validateメソッドにカスタムチェックを配置することで、検証エラーをスローすることもできます。次に、検証エラーと同様に、保存を妨げる権限エラーを処理できます。

1
Bryan

AuthorizeAttributeの構造を模倣できます。私の過去のプロジェクトの1つで、「許可」機能をさまざまなメソッドに分離し、それらのメソッドにカスタマイズされた許可属性を追加しました。したがって、コントローラーメソッドのサブ関数を承認する必要がある場合は、その機能を独自のメソッドに配置します。

属性のコンストラクターに静的データを渡して、許可可能なオブジェクトの識別子を示すことができます。属性は、要求可能なユーザー/サービスIDのセキュリティプリンシパルのコンテキストを考慮して、許可可能なオブジェクトに対して必要な許可ロジックを実行できます。 、およびセキュリティアクション。

承認方法はまったく異なります。実証済みの承認モデル(RBAC、ABAC、ReBACなど)を選択し、それらのモデルを実装する既存のオープンソースライブラリがあるかどうかを確認することをお勧めします。一般に、独自の承認モデルを考え出さないことをお勧めします。

0
Mackers

カスタム属性をCQRSおよびデコレーターパターンと組み合わせて使用​​しました。話は安いので、コデズをあげます。この属性はコントローラレベルで使用できることに注意してくださいORコマンドレベル、または本当に偏執狂の場合はその両方。

注意:これは私のコードのすべてではありません。SimpleInjector(Steven van Deursen)の作成者が、私がもう見つけられない投稿でこれに影響を与えました。これはすべてIoC(SimpleInjector)を介して配線されます。

/// <summary>
/// For the Command Side
/// </summary>
[Permission(Permission.StickyPost)]
public class StickyPostCommand
{
    public int PostId { get; set; }
}

public class StickyPostCommandHandler : ICommandHandler<StickyPostCommand>
{
    public void Handle(StickyPostCommand command)
    {
        //We only handle what we are here to handle (stickying a post), not checking permissions
    }
}

/// <summary>
/// This decorates all commands, and does some checks before passing it on to the real command (decoartee)
/// </summary>
/// <typeparam name="TCommand"></typeparam>
public class PermissionCommandHandlerDecorator<TCommand> : ICommandHandler<TCommand>
{
    private readonly MessagePermissionChecker<TCommand> permissionChecker;
    private readonly ICommandHandler<TCommand> decoratee;

    public PermissionCommandHandlerDecorator(MessagePermissionChecker<TCommand> permissionChecker, ICommandHandler<TCommand> decoratee)
    {
        this.decoratee = decoratee;
        this.permissionChecker = permissionChecker;
    }
    public void Handle(TCommand command)
    {
        this.permissionChecker.CheckPermissionForCurrentUser();
        this.decoratee.Handle(command);
    }
}

/// <summary>
/// Pulls the custom attribute out of the command, delegate to the UserPermissionChecker if it has an attribute
/// </summary>
/// <typeparam name="TMessage"></typeparam>
public class MessagePermissionChecker<TMessage>
{
    private static readonly Guid? permissionId;
    private readonly IUserPermissionChecker permissionChecker;

    static MessagePermissionChecker()
    {
        var permissionAttribute = typeof(TMessage).GetCustomAttribute<PermissionAttribute>();
        if (permissionAttribute != null)
        {
            permissionId = Guid.Parse(permissionAttribute.PermissionId);
        }
    }

    public MessagePermissionChecker(IUserPermissionChecker permissionChecker)
    {
        this.permissionChecker = permissionChecker;
    }

    public void CheckPermissionForCurrentUser()
    {
        if (permissionId.HasValue)
        {
            this.permissionChecker.CheckPermission(permissionId);
        }
    }
}

public interface IUserPermissionChecker
{
    void CheckPermission(Guid id);
}

public class UserPermissionChecker : IUserPermissionChecker
{
    public void CheckPermission(Guid id)
    {
        //Go out to the database, check a role, whatever
        throw new SecurityException("Negative ghostrider, you can't do that!");
    }
}

/// <summary>
/// For the Controller Side
/// </summary>
public class PermissionActionFilter : IActionFilter<PermissionAttribute>
{
    private readonly IUserPermissionChecker _permissionChecker;

    public PermissionActionFilter(IUserPermissionChecker permissionChecker)
    {
        _permissionChecker = permissionChecker;
    }

    public void OnActionExecuting(PermissionAttribute attribute, ActionExecutingContext context)
    {
        this._permissionChecker.CheckPermission(Guid.Parse(attribute.PermissionId));
    }
}
0
Jack