web-dev-qa-db-fra.com

Comment implémenter le contrôle d'accès basé sur les autorisations avec Asp.Net Core

J'essaye de mettre en application le contrôle d'accès basé par autorisation avec le noyau d'aspnet. Pour gérer de manière dynamique les rôles et les autorisations des utilisateurs (create_product, delete_product, etc.), ils sont stockés dans la base de données. Le modèle de données ressemble à http://i.stack.imgur.com/CHMPE.png

Avant d'aspnet core (dans MVC 5), j'utilisais AuthorizeAttribute personnalisé, comme ci-dessous, pour traiter le problème:

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;
    }
}

Ensuite, je l'utilisais dans la méthode d'action comme ci-dessous:

[HttpGet]
[CustomAuthorize(PermissionEnum.PERSON_LIST)]
public ActionResult Index(PersonListQuery query){ }

De plus, j'utilisais HttpContext.Items ["CUSTOM_USER"] dans les vues pour afficher ou masquer les parties html:

@if (CurrentUser.HasPermission("<Permission Name>"))
{

}

Quand j'ai décidé de changer d'aspnet core, tout mon plan a échoué. Parce qu'il n'y avait pas de méthode OnAuthorization virtuelle dans AuthorizeAttribute. J'ai essayé quelques façons de résoudre le problème. Ceux-ci sont ci-dessous:

  • Utilisation d'une nouvelle autorisation basée sur une stratégie (je pense que cela ne convient pas pour my scenerio)

  • Utilisation de AuthorizeAttribute et AuthorizationFilter personnalisés (je lis cecipost https://stackoverflow.com/a/35863514/5426333 mais je ne peux pas le modifier correctement)

  • Utilisation d'un middleware personnalisé (comment obtenir AuthorizeAttribute de l'action En cours?)

  • Utilisation de ActionFilter (est-ce correct pour des raisons de sécurité?)

Je ne pouvais pas décider de la meilleure façon pour mon scénario et comment l’appliquer.

Première question: La mise en œuvre de MVC5 est-elle une mauvaise pratique?

Deuxième question: Avez-vous une suggestion pour implémenter le noyau aspnet?

30
adem caglin

En fonction des commentaires, voici un exemple d'utilisation de l'autorisation basée sur une stratégie: 

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);
        }
    }
}

Et enregistrez-le dans votre classe 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>();

Et enfin dans le contrôleur

[HttpGet]
[Authorize(Policy = PermissionEnum.PERSON_LIST.ToString())]
public ActionResult Index(PersonListQuery query)
{
    ...
}

L’avantage de cette solution est que vous pouvez également avoir plusieurs gestionnaires pour une exigence, c’est-à-dire que si le premier réussit, le deuxième gestionnaire peut déterminer qu’il échoue et vous pouvez l’utiliser avec autorisation basée sur une ressource avec peu d’effort supplémentaire.

L’approche basée sur des stratégies est le moyen privilégié par l’équipe ASP.NET Core. 

De blowdart :

Nous ne voulons pas que vous écriviez des attributs d'autorisation personnalisés. Si vous avez besoin de faire cela, nous avons fait quelque chose de mal. Au lieu de cela, vous devriez écrire les conditions d'autorisation.

43
Tseng

Pour une solution qui ne vous oblige pas à ajouter une stratégie pour chaque autorisation, consultez mon answer pour une autre question.

Il vous permet de décorer vos contrôleurs et vos actions avec les attributs personnalisés de votre choix et d'y accéder dans votre AuthorizationHandler.

1
Shawn

J'ai eu la même exigence et je l'ai fait comme ci-dessous et cela fonctionne très bien pour moi. J'utilise .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;
    }
}

Dans Controller, utilisez-le comme ci-dessous 

        [HttpPost]
    [CheckAccess(Permission.CreateGroup)]
    public JsonResult POST([FromBody]Group group)
    {
       // your code api code here.
    }
1
vinayak shettar