web-dev-qa-db-fra.com

Comment créez-vous un AuthorizeAttribute personnalisé dans ASP.NET Core?

J'essaie de créer un attribut d'autorisation personnalisé dans ASP.NET Core. Dans les versions précédentes, il était possible de remplacer bool AuthorizeCore(HttpContextBase httpContext). Mais cela n'existe plus dans AuthorizeAttribute .

Quelle est l’approche actuelle pour créer un AuthorizeAttribute personnalisé?

Ce que j'essaie d'accomplir: je reçois un identifiant de session dans l'autorisation d'en-tête. À partir de cet identifiant, je saurai si une action particulière est valide.

329
jltrem

L’approche recommandée par l’équipe ASP.Net Core consiste à utiliser la nouvelle conception de la stratégie, qui est entièrement documentée ici . L’idée de base de la nouvelle approche est d’utiliser le nouvel attribut [Authorize] pour désigner une "stratégie" (par exemple, [Authorize( Policy = "YouNeedToBe18ToDoThis")] où la stratégie est enregistrée dans le fichier Startup.cs de l’application afin d’exécuter un bloc de code l’utilisateur a une demande d’âge lorsque l’âge est de 18 ans ou plus).

La conception de la politique est un excellent ajout au cadre et il convient de féliciter l'équipe ASP.Net Security Core pour sa mise en place. Cela dit, cela ne convient pas à tous les cas. L'inconvénient de cette approche est qu'elle ne fournit pas de solution pratique au besoin le plus courant consistant à simplement affirmer qu'un contrôleur ou une action donné nécessite un type de revendication donné. Dans le cas où une application peut disposer de centaines d'autorisations distinctes régissant les opérations CRUD sur des ressources REST individuelles ("CanCreateOrder", "CanReadOrder", "CanUpdateOrder", "CanDeleteOrder", etc.), la nouvelle approche nécessite des correspondances répétitives entre un nom de politique et un nom de revendication (par exemple options.AddPolicy("CanUpdateOrder", policy => policy.RequireClaim(MyClaimTypes.Permission, "CanUpdateOrder));), ou l'écriture d'un code pour effectuer ces enregistrements au moment de l'exécution (par exemple, lire tous les types de revendications d'une base de données et exécuter l'appel susmentionné en boucle). Le problème avec cette approche dans la majorité des cas, c’est que c’est une surcharge inutile.

Bien que l'équipe ASP.Net Core Security recommande de ne jamais créer votre propre solution, dans certains cas, il peut s'agir de l'option la plus prudente.

Ce qui suit est une implémentation qui utilise le filtre IAuthorizationFilter pour fournir un moyen simple d'exprimer une exigence de réclamation pour un contrôleur ou une action donné:

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();
    }
}
324
Derek Greer

Je suis le responsable de la sécurité asp.net. Tout d'abord, permettez-moi de m'excuser, rien de tout cela n'est encore documenté en dehors de l'échantillon ou des tests unitaires du magasin de musique, et tout cela est encore affiné en termes d'API exposées. La documentation détaillée est ici .

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 .

L'autorisation agit sur les identités. Les identités sont créées par authentification.

Vous dites dans les commentaires que vous souhaitez vérifier un identifiant de session dans un en-tête. Votre identifiant de session serait la base de l'identité. Si vous voulez utiliser l'attribut Authorize, vous devez écrire un middleware d'authentification pour extraire cet en-tête et le transformer en un ClaimsPrincipal authentifié. Vous vérifierez ensuite cela dans une exigence d'autorisation. Les exigences en matière d'autorisation peuvent être aussi compliquées que vous le souhaitez, par exemple, voici l'une des revendications qui identifie l'identité actuelle en fonction de la date de naissance et qui autorise si l'utilisateur a plus de 18 ans;

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

Ensuite, dans votre fonction ConfigureServices(), vous la raccorderiez

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

Et enfin, appliquez-le à un contrôleur ou à une méthode d'action avec

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

Il semble qu'avec ASP.NET Core 2, vous pouvez à nouveau hériter de AuthorizeAttribute, il vous suffit simplement d'implémenter également IAuthorizationFilter (ou 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;
        }
    }
}
73
gius

Vous pouvez créer votre propre AuthorizationHandler qui trouvera des attributs personnalisés sur vos contrôleurs et actions et les transmettra à la méthode HandleRequirementAsync.

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

Vous pouvez ensuite l'utiliser pour tous les attributs personnalisés dont vous avez besoin sur vos contrôleurs ou vos actions. Par exemple, pour ajouter des conditions d'autorisation. Créez simplement votre attribut personnalisé.

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

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

Puis créez une exigence à ajouter à votre politique

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

Créez ensuite le AuthorizationHandler pour votre attribut personnalisé, en héritant du AttributeAuthorizationHandler créé précédemment. Un IEnumerable lui sera transmis pour tous vos attributs personnalisés dans la méthode HandleRequirementsAsync, accumulés à partir de votre contrôleur et de votre 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
    }
}

Enfin, dans votre méthode ConfigureServices Startup.cs, ajoutez votre AuthorizationHandler personnalisé aux services, puis ajoutez votre stratégie.

        services.AddSingleton<IAuthorizationHandler, PermissionAuthorizationHandler>();

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

Maintenant, vous pouvez simplement décorer vos contrôleurs et actions avec votre attribut personnalisé.

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

Quelle est l’approche actuelle pour créer un AuthorizeAttribute personnalisé?

Facile: ne créez pas votre propre AuthorizeAttribute.

Pour les scénarios d'autorisation pure (comme restreindre l'accès à des utilisateurs spécifiques uniquement), l'approche recommandée consiste à utiliser le nouveau bloc d'autorisation: 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() { ... }
}

Pour l'authentification, il est préférable de gérer au niveau du middleware.

Qu'essayez-vous de réaliser exactement?

23
Pinpoint

Basé sur Derek Greer GREAT answer, je l'ai fait avec des enums.

Voici un exemple de mon code:

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());
    }
}
23
bruno.almeida