it-swarm.com.de

Wie erstellen Sie ein benutzerdefiniertes AuthorizeAttribute in ASP.NET Core?

Ich versuche, ein benutzerdefiniertes Berechtigungsattribut in ASP.NET Core zu erstellen. In früheren Versionen war es möglich, bool AuthorizeCore(HttpContextBase httpContext) zu überschreiben. Dies existiert aber nicht mehr in AuthorizeAttribute .

Wie wird derzeit ein benutzerdefiniertes AuthorizeAttribute erstellt?

Was ich versuche zu erreichen: Ich erhalte eine Sitzungs-ID in der Header-Autorisierung. Anhand dieser ID werde ich wissen, ob eine bestimmte Aktion gültig ist.

329
jltrem

Der vom ASP.Net Core-Team empfohlene Ansatz besteht in der Verwendung des neuen Richtlinienentwurfs, der vollständig dokumentiert ist hier . Die Grundidee des neuen Ansatzes besteht darin, mit dem neuen Attribut [Authorize] eine "Richtlinie" zu bestimmen (z. B. [Authorize( Policy = "YouNeedToBe18ToDoThis")], in der die Richtlinie in Startup.cs der Anwendung registriert ist, um einen Codeblock auszuführen (d. H Der Benutzer hat einen Altersanspruch, wenn er 18 Jahre oder älter ist.

Das Richtlinien-Design ist eine großartige Ergänzung des Frameworks und das ASP.Net Security Core-Team sollte für seine Einführung gelobt werden. Das heißt, es ist nicht für alle Fälle gut geeignet. Das Manko dieses Ansatzes ist, dass es keine bequeme Lösung für das häufigste Bedürfnis bietet, einfach zu behaupten, dass ein bestimmter Controller oder eine bestimmte Aktion einen bestimmten Anspruchstyp erfordert. In dem Fall, in dem eine Anwendung möglicherweise Hunderte von diskreten Berechtigungen für CRUD-Vorgänge für einzelne REST Ressourcen besitzt ("CanCreateOrder", "CanReadOrder", "CanUpdateOrder", "CanDeleteOrder" usw.), kann der neue Ansatz verwendet werden erfordert wiederholte Eins-zu-Eins-Zuordnungen zwischen einem Richtliniennamen und einem Anspruchsnamen (z. B. options.AddPolicy("CanUpdateOrder", policy => policy.RequireClaim(MyClaimTypes.Permission, "CanUpdateOrder));) oder das Schreiben von Code, um diese Registrierungen zur Laufzeit durchzuführen (z. B. Lesen aller Anspruchstypen aus einer Datenbank und Ausführen des oben genannten Aufrufs) in einer Schleife). Das Problem bei diesem Ansatz ist in den meisten Fällen, dass es unnötigen Overhead gibt.

Das ASP.Net Core Security-Team empfiehlt zwar, niemals eine eigene Lösung zu erstellen, in einigen Fällen ist dies jedoch die vernünftigste Option, mit der Sie beginnen sollten.

Die folgende Implementierung verwendet den IAuthorizationFilter, um auf einfache Weise eine Anspruchsanforderung für einen bestimmten Controller oder eine bestimmte Aktion auszudrücken:

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

Ich bin die asp.net Sicherheitsperson. Lassen Sie mich zunächst entschuldigen, dass dies alles noch nicht außerhalb der Muster- oder Komponententests des Musikgeschäfts dokumentiert ist und dass es immer noch in Bezug auf offen gelegte APIs verfeinert wird. Detaillierte Dokumentation ist hier .

Wir möchten nicht, dass Sie benutzerdefinierte Berechtigungsattribute schreiben. Wenn Sie das tun müssen, haben wir etwas falsch gemacht. Stattdessen sollten Sie Autorisierungsanforderungen schreiben .

Die Autorisierung wirkt auf Identitäten. Identitäten werden durch Authentifizierung erstellt.

In Kommentaren geben Sie an, dass Sie eine Sitzungs-ID in einem Header überprüfen möchten. Ihre Sitzungs-ID wäre die Basis für die Identität. Wenn Sie das Attribut Authorize verwenden möchten, schreiben Sie eine Authentifizierungs-Middleware, um diesen Header in einen authentifizierten ClaimsPrincipal umzuwandeln. Sie würden dies dann innerhalb einer Berechtigungsanforderung überprüfen. Die Autorisierungsanforderungen können so kompliziert sein, wie Sie möchten. Hier ist beispielsweise eine, die einen Geburtsdatum-Anspruch auf die aktuelle Identität erhebt und autorisiert, wenn der Benutzer über 18 Jahre alt ist.

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

Dann würden Sie es in Ihrer Funktion ConfigureServices() verkabeln

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

Und schließlich wenden Sie es mit auf einen Controller oder eine Aktionsmethode an

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

Es scheint, dass Sie mit ASP.NET Core 2 wieder AuthorizeAttribute erben können. Sie müssen lediglich IAuthorizationFilter (oder IAsyncAuthorizationFilter) implementieren:

[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

Sie können Ihren eigenen AuthorizationHandler erstellen, der benutzerdefinierte Attribute auf Ihren Controllern und Aktionen findet und diese an die HandleRequirementAsync-Methode übergibt.

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

Dann können Sie es für alle benutzerdefinierten Attribute verwenden, die Sie für Ihre Controller oder Aktionen benötigen. Zum Beispiel, um Berechtigungsanforderungen hinzuzufügen. Erstellen Sie einfach Ihr benutzerdefiniertes Attribut.

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

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

Erstellen Sie dann eine Anforderung, die Sie Ihrer Richtlinie hinzufügen möchten

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

Erstellen Sie dann den AuthorizationHandler für Ihr benutzerdefiniertes Attribut und erben Sie den zuvor erstellten AttributeAuthorizationHandler. Es wird eine IEnumerable für alle Ihre benutzerdefinierten Attribute in der HandleRequirementsAsync-Methode übergeben, die von Ihrem Controller und Ihrer Aktion gesammelt wurden.

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

Fügen Sie schließlich in Ihrer Startup.cs ConfigureServices-Methode Ihren benutzerdefinierten AuthorizationHandler zu den Diensten hinzu und fügen Sie Ihre Richtlinie hinzu.

        services.AddSingleton<IAuthorizationHandler, PermissionAuthorizationHandler>();

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

Jetzt können Sie Ihre Controller und Aktionen einfach mit Ihrem benutzerdefinierten Attribut dekorieren.

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

Was ist der aktuelle Ansatz, um ein benutzerdefiniertes AuthorizeAttribute zu erstellen?

Ganz einfach: Erstellen Sie nicht Ihr eigenes AuthorizeAttribute.

Für reine Autorisierungsszenarien (z. B. Einschränkung des Zugriffs nur auf bestimmte Benutzer) wird die Verwendung des neuen Autorisierungsblocks empfohlen: 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() { ... }
}

Für die Authentifizierung empfiehlt sich die Verwendung auf Middleware-Ebene.

Was versuchst du genau zu erreichen?

23
Pinpoint

Basierend auf der Antwort von Derek Greer GREAT habe ich es mit Aufzählungen gemacht.

Hier ist ein Beispiel für meinen 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