Como você cria um AuthorizeAttribute personalizado no ASP.NET Core?

428

Estou tentando criar um atributo de autorização personalizado no ASP.NET Core. Nas versões anteriores, era possível substituir bool AuthorizeCore(HttpContextBase httpContext). Mas isso não existe mais no AuthorizeAttribute.

Qual é a abordagem atual para criar um AuthorizeAttribute personalizado?

O que estou tentando realizar: estou recebendo um ID de sessão na Autorização de Cabeçalho. A partir desse ID, saberei se uma ação específica é válida.

jltrem
fonte
Não sei como fazê-lo, mas o MVC é de código aberto. Você pode obter o repositório do github e procurar implementações do IAuthorizationFilter. Se eu tiver tempo hoje, procurarei por você e postarei uma resposta real, mas sem promessas. repo do github: github.com/aspnet/Mvc
bopapa_1979
OK, sem tempo, mas procure os usos de AuthorizationPolicy no MVC Repo, que usa AuthorizeAttribute, no repositório aspnet / Security, aqui: github.com/aspnet/Security . Como alternativa, procure no repositório MVC o espaço para nome em que os itens de segurança mais importantes parecem residir, que é o Microsoft.AspNet.Authorization. Desculpe, não posso ser mais útil. Boa sorte!
Bopapa_1979 16/07/2015

Respostas:

446

A abordagem recomendada pela equipe do ASP.Net Core é usar o novo design de política, que está totalmente documentado aqui . A idéia básica por trás da nova abordagem é usar o novo atributo [Autorizar] para designar uma "política" (por exemplo, [Authorize( Policy = "YouNeedToBe18ToDoThis")]onde a política está registrada no Startup.cs do aplicativo para executar algum bloco de código (por exemplo, garantir que o usuário tenha uma reivindicação de idade) com idade igual ou superior a 18 anos).

O design da política é um ótimo complemento para a estrutura e a equipe do ASP.Net Security Core deve ser elogiada por sua introdução. Dito isto, não é adequado para todos os casos. A desvantagem dessa abordagem é que ela falha em fornecer uma solução conveniente para a necessidade mais comum de simplesmente afirmar que um determinado controlador ou ação requer um determinado tipo de declaração. No caso em que um aplicativo pode ter centenas de permissões discretas que controlam operações CRUD em recursos REST individuais ("CanCreateOrder", "CanReadOrder", "CanUpdateOrder", "CanDeleteOrder" etc.), a nova abordagem exige repetições um a um um mapeamento entre um nome de política e um nome de reivindicação (por exemplo,options.AddPolicy("CanUpdateOrder", policy => policy.RequireClaim(MyClaimTypes.Permission, "CanUpdateOrder));) ou escrevendo algum código para executar esses registros em tempo de execução (por exemplo, leia todos os tipos de declaração de um banco de dados e execute a chamada acima em um loop). O problema com essa abordagem na maioria dos casos é que ela é desnecessária.

Embora a equipe do ASP.Net Core Security recomende nunca criar sua própria solução, em alguns casos, essa pode ser a opção mais prudente para começar.

A seguir, uma implementação que usa o IAuthorizationFilter para fornecer uma maneira simples de expressar um requisito de declaração para um determinado controlador ou ação:

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();
    }
}
Derek Greer
fonte
79
Isso deve ser marcado como a RESPOSTA CORRETA. Aqui você vê como as pessoas da Microsoft consideram o feedback dos desenvolvedores. Não entendo o motivo pelo qual eles são tão "fechados" ao redor disso, pois é uma situação muito comum ter uma miríade de permissões diferentes, ter que codificar uma política para cada uma é um exagero completo. Eu estava procurando isso por tanto tempo ... (Eu já fiz essa pergunta há quase dois anos, quando o vNext ainda era uma aposta aqui: stackoverflow.com/questions/32181400/…, mas ainda estamos presos lá)
Vi100
3
Isso é bom. Temos um middleware de autenticação na API da Web, mas agregamos segurança às permissões de autorização por função; portanto, basta inserir um atributo como: [MyAuthorize (MyClaimTypes.Permission, MyClaimValueTypes.Write, MyPermission.Employee)] parece muito bem.
Mariano Peinador
4
@Derek Greer: Esta é a melhor resposta. No entanto, você implementa um ActionFilter que é executado após o Autorizar filtro de ação. Existe alguma maneira de implementar e autorizar filtro de ação?
Jacob Phan
6
@JacobPhan Você está correto, isso seria melhor implementado usando a interface IAuthorizationFilter. Atualizei o código para refletir as alterações.
Derek Greer
3
portanto new ForbidResult(), não funciona (causa exceção / 500) porque não possui um esquema de autorização associado. O que eu usaria para este caso?
Sinaesthetic
252

Eu sou a pessoa de segurança asp.net. Em primeiro lugar, peço desculpas por nada disso estar documentado ainda fora da amostra da loja de música ou dos testes de unidade, e tudo ainda está sendo refinado em termos de APIs expostas. A documentação detalhada está aqui .

Não queremos que você escreva atributos de autorização personalizados. Se você precisar fazer isso, fizemos algo errado. Em vez disso, você deve escrever requisitos de autorização .

A autorização atua sobre identidades. Identidades são criadas por autenticação.

Você diz nos comentários que deseja verificar um ID de sessão em um cabeçalho. O seu ID de sessão seria a base da identidade. Se você quisesse usar o Authorizeatributo, escreveria um middleware de autenticação para pegar esse cabeçalho e transformá-lo em um autenticado ClaimsPrincipal. Você então verificaria isso dentro de um requisito de autorização. Os requisitos de autorização podem ser tão complicados quanto você desejar, por exemplo, aqui está um que aceita uma reivindicação de data de nascimento na identidade atual e autoriza se o usuário tiver mais de 18 anos;

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

Então, na sua ConfigureServices()função, você ligaria

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

E, finalmente, aplique-o a um controlador ou método de ação com

[Authorize(Policy = "Over18")]
blowdart
fonte
84
Eu me pergunto ... como alguém implementaria um controle de acesso refinado com isso? Digamos a ManageStoreamostra Requisito da Music Store. Como está na amostra, existe apenas uma maneira de "permitir tudo ou nada" para fazê-lo. Temos então que criar uma nova política para todas as permutações possíveis? ou seja, "Usuários / Leitura", "Usuários / Criar", "Usuários / AtribuirRole", "Usuários / Excluir" se quisermos reivindicações refinadas? Parece um trabalho de configuração para fazê-lo funcionar e abundância de políticas apenas para gerenciar reivindicações em vez de um [ClaimsAutzorization("User", "Read", "Create", "Delete", "Assign")]atributo?
Tseng
84
Devo comentar que tudo isso é mais complexo do que implementar um método de autorização personalizado. Eu sei como eu quero que a autorização seja feita. Eu poderia simplesmente escrever no MVC 5, no MVC 6 eles adicionam muito código "concluído" que é realmente mais complexo de entender do que implementar a "coisa" principal em si. Me deixa sentado na frente de uma página tentando descobrir alguma coisa em vez de escrever o código, também é uma grande dor para as pessoas que usam RDBMS que não sejam da Microsoft (ou No-Sql).
Felype
17
Do meu ponto de vista, isso não resolve todos os cenários. Antes do MVC 6, usei um Atributo de autorização personalizado, para implementar meu próprio "Sistema de permissão". Eu poderia adicionar o atributo Authorize a todas as ações e passar uma permissão específica necessária (como Enum-Value). A permissão em si é mapeada para grupos / usuários no banco de dados. Portanto, não vejo uma maneira de lidar com isso com políticas !?
Gerwald 17/01
43
Eu, como muitos outros comentários, estou muito desapontado que o uso de atributos para autorização tenha sido tão neutralizado quanto o que era possível na Web API 2. Desculpe pessoal, mas sua abstração de "requisitos" falha em cobrir qualquer caso em que poderíamos usar anteriormente Atribuir parâmetros do construtor para informar um algoritmo de autorização subjacente. Costumava ser simples com morte cerebral fazer algo assim [CustomAuthorize(Operator.And, Permission.GetUser, Permission.ModifyUser)]. Eu poderia usar um único atributo personalizado de várias maneiras, simplesmente modificando os parâmetros do construtor.
NathanAldenSr
61
Também estou chocado que o auto-proclamado "líder da segurança do ASP.NET" esteja realmente sugerindo o uso de seqüências de caracteres mágicas (invadindo o significado de IAuthorizeData.Policy) e provedores de políticas personalizadas para superar essa flagrante supervisão, em vez de abordá-la dentro da estrutura. Eu pensei que não deveríamos estar criando nossas próprias implementações? Você não deixou escolha para vários de nós, exceto para reimplementar a autorização do zero (novamente), e desta vez sem o benefício do Authorizeatributo antigo da API da Web . Agora temos que fazer isso no nível do filtro de ação ou do middleware.
NathanAldenSr
104

Parece que, com o ASP.NET Core 2, você pode herdar novamente AuthorizeAttribute, basta implementar 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;
        }
    }
}
gius
fonte
4
Então você só pode usar isso para negar autorização, não para conceder ?
MEMark
1
@MEMark Ao conceder , você quer dizer substituir outro atributo de autorização?
gius
2
AFAIK, o acesso é permitido por padrão, portanto, você precisa negá-lo explicitamente (por exemplo, adicionando um AuthorizeAttribute). Verifique esta pergunta para obter mais detalhes: stackoverflow.com/questions/17272422/…
gius
16
Observe também que, no exemplo sugerido, não é preciso herdar de AuthorizeAttribute. Você pode herdar de Attribute e IAuthorizationFilter . Dessa forma, você não obteria a seguinte exceção se algum mecanismo de autenticação não padrão fosse usado: InvalidOperationException: Nenhum authenticationScheme foi especificado e não foi encontrado DefaultChallengeScheme.
Anatolyevich
13
Observe que, se sua OnAuthorizationimplementação precisar aguardar um método assíncrono, você deve implementar, em IAsyncAuthorizationFiltervez disso, IAuthorizationFilterseu filtro será executado de forma síncrona e a ação do controlador será executada independentemente do resultado do filtro.
Codemunkie 23/03/19
34

Baseado na excelente resposta de Derek Greer , eu fiz isso com enumerações.

Aqui está um exemplo do meu código:

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());
    }
}
bruno.almeida
fonte
1
Obrigado por isso. Eu criei este post com uma implementação ligeiramente diferente e um pedido de validação stackoverflow.com/questions/49551047/...
Anton Swanevelder
2
MumboJumboFunction <3
Marek Urbanowicz
31

Você pode criar seu próprio AuthorizationHandler que encontrará atributos personalizados em seus Controladores e Ações e passá-los para o método 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>();
    }
}

Em seguida, você pode usá-lo para qualquer atributo personalizado necessário em seus controladores ou ações. Por exemplo, para adicionar requisitos de permissão. Basta criar seu atributo personalizado.

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

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

Em seguida, crie um requisito para adicionar à sua política

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

Em seguida, crie o AuthorizationHandler para seu atributo personalizado, herdando o AttributeAuthorizationHandler que criamos anteriormente. Será passado um IEnumerable para todos os seus atributos personalizados no método HandleRequirementsAsync, acumulado do seu Controller e 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
    }
}

E finalmente, no seu método Startup.cs ConfigureServices, adicione seu AuthorizationHandler personalizado aos serviços e adicione sua Política.

        services.AddSingleton<IAuthorizationHandler, PermissionAuthorizationHandler>();

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

Agora você pode simplesmente decorar seus Controladores e Ações com seu atributo personalizado.

[Permission("AccessCustomers")]
public class CustomersController
{
    [Permission("AddCustomer")]
    IActionResult AddCustomer([FromBody] Customer customer)
    {
        //Add customer
    }
}
Shawn
fonte
1
Vou dar uma olhada neste mais rápido possível.
NathanAldenSr
5
Isso é superengenharia ... Resolvi o mesmo usando um simples AuthorizationFilterAttribute que recebe um parâmetro. Você não precisa de reflexão para isso, parece ainda mais artificial do que a solução "oficial" (que eu acho bastante pobre).
Vi100
2
@ Vi100 Não consegui encontrar muita informação sobre os AuthorizationFilters no ASP.NET Core. A página da documentação oficial diz que eles estão trabalhando neste tópico. docs.microsoft.com/en-us/aspnet/core/security/authorization/…
Shawn
4
@ Vi100 Você pode compartilhar sua solução, se houver uma maneira mais simples de conseguir isso, eu gostaria de saber.
Shawn
2
Uma coisa a observar o uso do UnderlyingSystemType acima não é compilado, mas sua remoção parece funcionar.
hora do chá 21/06
25

Qual é a abordagem atual para criar um AuthorizeAttribute personalizado

Fácil: não crie o seu próprio AuthorizeAttribute .

Para cenários de autorização pura (como restringir o acesso apenas a usuários específicos), a abordagem recomendada é usar o novo bloco de autorização: 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() { ... }
}

Para autenticação, é melhor lidar com o nível do middleware.

O que você está tentando alcançar exatamente?

Kévin Chalet
fonte
1
Estou recebendo um ID de sessão na Autorização de Cabeçalho. A partir desse ID, saberei se uma ação específica é válida.
jltrem
1
Então isso não é uma preocupação de autorização. Eu acho que seu "ID da sessão" é realmente um token que contém a identidade do chamador: isso definitivamente deve ser feito no nível do middleware.
Kévin Chalet
3
Não é autenticação (estabelecendo quem é o usuário), mas é autorização (determinando se um usuário deve ter acesso a um recurso). Então, onde você está sugerindo que eu procure resolver isso?
Jltrem
3
@ jltrem, concordou, o que você está falando é autorização, não autenticação.
bopapa_1979
2
@ Pinpoint eu não sou. Eu consulto outro sistema para essa informação. Esse sistema autentica (determina o usuário) e autoriza (diz-me o que esse usuário pode acessar). No momento, ele foi hackeado para funcionar, chamando um método em cada ação do controlador para que o outro sistema verifique a sessão. Eu gostaria que isso acontecesse automaticamente através de um atributo.
Jltrem
4

Se alguém apenas deseja validar um token de portador na fase de autorização usando as práticas de segurança atuais, você pode,

adicione isso ao seu Startup / ConfigureServices

    services.AddSingleton<IAuthorizationHandler, BearerAuthorizationHandler>();
    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer();

    services.AddAuthorization(options => options.AddPolicy("Bearer",
        policy => policy.AddRequirements(new BearerRequirement())
        )
    );

e isso na sua base de código,

public class BearerRequirement : IAuthorizationRequirement
{
    public async Task<bool> IsTokenValid(SomeValidationContext context, string token)
    {
        // here you can check if the token received is valid 
        return true;
    }
}

public class BearerAuthorizationHandler : AuthorizationHandler<BearerRequirement> 
{

    public BearerAuthorizationHandler(SomeValidationContext thatYouCanInject)
    {
       ...
    }

    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, BearerRequirement requirement)
    {
        var authFilterCtx = (Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext)context.Resource;
        string authHeader = authFilterCtx.HttpContext.Request.Headers["Authorization"];
        if (authHeader != null && authHeader.Contains("Bearer"))
        {
            var token = authHeader.Replace("Bearer ", string.Empty);
            if (await requirement.IsTokenValid(thatYouCanInject, token))
            {
                context.Succeed(requirement);
            }
        }
    }
}

Se o código não atingir context.Succeed(...) , falhará de qualquer maneira (401).

E então em seus controladores você pode usar

 [Authorize(Policy = "Bearer", AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
Gabriel P.
fonte
Por que você escolheria executar sua própria validação do token quando o middleware JwtBearer já cuida disso? Ele também coloca o conteúdo correto no cabeçalho de resposta WWW-Authenticate para uma falha de validação / expiração de autenticação / token. Se você deseja acessar o pipeline de autenticação, há eventos específicos nos quais você pode explorar as opções AddJwtBearer (OnAuthenticationFailed, OnChallenge, OnMessageReceived e OnTokenValidated).
Darren Lewis
Isso é infinitamente mais simples do que qualquer outra solução que eu já tenha visto. Especialmente para casos de uso simples de chave API. Uma atualização: para 3.1, a conversão para AuthorizationFilterContext não é mais válida devido ao material de roteamento do terminal. Você precisa capturar o contexto via HttpContextAccessor.
JasonCoder
2

A maneira moderna é AuthenticationHandlers

em startup.cs add

services.AddAuthentication("BasicAuthentication").AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("BasicAuthentication", null);

public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
    {
        private readonly IUserService _userService;

        public BasicAuthenticationHandler(
            IOptionsMonitor<AuthenticationSchemeOptions> options,
            ILoggerFactory logger,
            UrlEncoder encoder,
            ISystemClock clock,
            IUserService userService)
            : base(options, logger, encoder, clock)
        {
            _userService = userService;
        }

        protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
        {
            if (!Request.Headers.ContainsKey("Authorization"))
                return AuthenticateResult.Fail("Missing Authorization Header");

            User user = null;
            try
            {
                var authHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]);
                var credentialBytes = Convert.FromBase64String(authHeader.Parameter);
                var credentials = Encoding.UTF8.GetString(credentialBytes).Split(new[] { ':' }, 2);
                var username = credentials[0];
                var password = credentials[1];
                user = await _userService.Authenticate(username, password);
            }
            catch
            {
                return AuthenticateResult.Fail("Invalid Authorization Header");
            }

            if (user == null)
                return AuthenticateResult.Fail("Invalid User-name or Password");

            var claims = new[] {
                new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
                new Claim(ClaimTypes.Name, user.Username),
            };
            var identity = new ClaimsIdentity(claims, Scheme.Name);
            var principal = new ClaimsPrincipal(identity);
            var ticket = new AuthenticationTicket(principal, Scheme.Name);

            return AuthenticateResult.Success(ticket);
        }
    }

IUserService é um serviço que você faz onde possui nome de usuário e senha. basicamente, ele retorna uma classe de usuário que você usa para mapear suas reivindicações.

var claims = new[] {
                new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
                new Claim(ClaimTypes.Name, user.Username),
            }; 

Em seguida, você pode consultar essas declarações e todos os dados que você mapeou, existem alguns, dê uma olhada na classe ClaimTypes

você pode usar isso em um método de extensão e obter qualquer um dos mapeamentos

public int? GetUserId()
{
   if (context.User.Identity.IsAuthenticated)
    {
       var id=context.User.FindFirst(ClaimTypes.NameIdentifier);
       if (!(id is null) && int.TryParse(id.Value, out var userId))
            return userId;
     }
      return new Nullable<int>();
 }

Essa nova maneira, eu acho que é melhor do que

public class BasicAuthenticationAttribute : AuthorizationFilterAttribute
{
    public override void OnAuthorization(HttpActionContext actionContext)
    {
        if (actionContext.Request.Headers.Authorization != null)
        {
            var authToken = actionContext.Request.Headers.Authorization.Parameter;
            // decoding authToken we get decode value in 'Username:Password' format
            var decodeauthToken = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(authToken));
            // spliting decodeauthToken using ':'
            var arrUserNameandPassword = decodeauthToken.Split(':');
            // at 0th postion of array we get username and at 1st we get password
            if (IsAuthorizedUser(arrUserNameandPassword[0], arrUserNameandPassword[1]))
            {
                // setting current principle
                Thread.CurrentPrincipal = new GenericPrincipal(new GenericIdentity(arrUserNameandPassword[0]), null);
            }
            else
            {
                actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
            }
        }
        else
        {
            actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
        }
    }

    public static bool IsAuthorizedUser(string Username, string Password)
    {
        // In this method we can handle our database logic here...
        return Username.Equals("test") && Password == "test";
    }
}
Walter Vehoeven
fonte
Essa resposta brilhante funciona como um encanto! Obrigado por isso e desejo que seja votado, pois é a melhor resposta que encontrei depois de seis horas pesquisando em blogs, documentação e pilha de autenticação básica e autorização de função.
Piotr Śródka 30/03
@ PiotrŚródka, você é bem-vindo, observe que a resposta é um pouco "simplificada", teste se você tem um ':' no texto, pois um usuário mal-intencionado pode tentar travar o serviço simplesmente não tendo um final agradável em um índice de exceção de intervalo. como sempre teste o que lhe é dado por fontes externas
Walter Vehoeven
2

Até o momento em que escrevo, acredito que isso possa ser conseguido com a interface IClaimsTransformation no asp.net core 2 e acima. Acabei de implementar uma prova de conceito que é compartilhável o suficiente para postar aqui.

public class PrivilegesToClaimsTransformer : IClaimsTransformation
{
    private readonly IPrivilegeProvider privilegeProvider;
    public const string DidItClaim = "http://foo.bar/privileges/resolved";

    public PrivilegesToClaimsTransformer(IPrivilegeProvider privilegeProvider)
    {
        this.privilegeProvider = privilegeProvider;
    }

    public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
    {
        if (principal.Identity is ClaimsIdentity claimer)
        {
            if (claimer.HasClaim(DidItClaim, bool.TrueString))
            {
                return principal;
            }

            var privileges = await this.privilegeProvider.GetPrivileges( ... );
            claimer.AddClaim(new Claim(DidItClaim, bool.TrueString));

            foreach (var privilegeAsRole in privileges)
            {
                claimer.AddClaim(new Claim(ClaimTypes.Role /*"http://schemas.microsoft.com/ws/2008/06/identity/claims/role" */, privilegeAsRole));
            }
        }

        return principal;
    }
}

Para usar isso no seu Controller, basta adicionar um apropriado [Authorize(Roles="whatever")]aos seus métodos.

[HttpGet]
[Route("poc")]
[Authorize(Roles = "plugh,blast")]
public JsonResult PocAuthorization()
{
    var result = Json(new
    {
        when = DateTime.UtcNow,
    });

    result.StatusCode = (int)HttpStatusCode.OK;

    return result;
}

No nosso caso, toda solicitação inclui um cabeçalho de autorização que é um JWT. Este é o protótipo e acredito que faremos algo super próximo disso em nosso sistema de produção na próxima semana.

Futuros eleitores, considere a data da redação ao votar. A partir de hoje, works on my machine.você provavelmente desejará mais tratamento e registro de erros em sua implementação.

Sem Reembolsos Sem Devoluções
fonte
E o ConfigureServices? É necessário adicionar algo?
Daniel
Como discutido em outro lugar, sim.
Sem Reembolsos Sem Retornos
1

Para autorização em nosso aplicativo. Tivemos que chamar um serviço com base nos parâmetros passados ​​no atributo de autorização.

Por exemplo, se quisermos verificar se o médico que está logado pode ver as consultas do paciente, passaremos "View_Appointment" para customizar o atributo de autorização e verificaremos se o serviço de DB está correto e com base nos resultados que iremos autorizar. Aqui está o código para este cenário:

    public class PatientAuthorizeAttribute : TypeFilterAttribute
    {
    public PatientAuthorizeAttribute(params PatientAccessRights[] right) : base(typeof(AuthFilter)) //PatientAccessRights is an enum
    {
        Arguments = new object[] { right };
    }

    private class AuthFilter : IActionFilter
    {
        PatientAccessRights[] right;

        IAuthService authService;

        public AuthFilter(IAuthService authService, PatientAccessRights[] right)
        {
            this.right = right;
            this.authService = authService;
        }

        public void OnActionExecuted(ActionExecutedContext context)
        {
        }

        public void OnActionExecuting(ActionExecutingContext context)
        {
            var allparameters = context.ActionArguments.Values;
            if (allparameters.Count() == 1)
            {
                var param = allparameters.First();
                if (typeof(IPatientRequest).IsAssignableFrom(param.GetType()))
                {
                    IPatientRequest patientRequestInfo = (IPatientRequest)param;
                    PatientAccessRequest userAccessRequest = new PatientAccessRequest();
                    userAccessRequest.Rights = right;
                    userAccessRequest.MemberID = patientRequestInfo.PatientID;
                    var result = authService.CheckUserPatientAccess(userAccessRequest).Result; //this calls DB service to check from DB
                    if (result.Status == ReturnType.Failure)
                    {
                        //TODO: return apirepsonse
                        context.Result = new StatusCodeResult((int)System.Net.HttpStatusCode.Forbidden);
                    }
                }
                else
                {
                    throw new AppSystemException("PatientAuthorizeAttribute not supported");
                }
            }
            else
            {
                throw new AppSystemException("PatientAuthorizeAttribute not supported");
            }
        }
    }
}

E na ação da API, usamos assim:

    [PatientAuthorize(PatientAccessRights.PATIENT_VIEW_APPOINTMENTS)] //this is enum, we can pass multiple
    [HttpPost]
    public SomeReturnType ViewAppointments()
    {

    }
Abdullah
fonte
1
Observe que IActionFilter será um problema quando você desejar usar o mesmo atributo para métodos de Hub em Hubs SignalR.SignalR esperam IAuthorizationFilter
ilkerkaran
Obrigado pela informação. No momento não estou usando o SignalR no meu aplicativo, então não o testei com ele.
Abdullah
Mesmo princípio, acho que como você ainda precisará usar a entrada de autorização do cabeçalho, a implementação será diferente
Walter Vehoeven
0

A resposta aceita ( https://stackoverflow.com/a/41348219/4974715 ) não é realisticamente mantida ou adequada porque "CanReadResource" está sendo usado como uma reivindicação (mas deve ser essencialmente uma política na realidade, IMO). A abordagem na resposta não é boa na maneira como foi usada, porque se um método de ação exigir muitas configurações de declarações diferentes, com essa resposta, você deverá escrever repetidamente algo como ...

[ClaimRequirement(MyClaimTypes.Permission, "CanReadResource")] 
[ClaimRequirement(MyClaimTypes.AnotherPermision, "AnotherClaimVaue")]
//and etc. on a single action.

Então, imagine quanta codificação seria necessária. Idealmente, "CanReadResource" deve ser uma política que use muitas declarações para determinar se um usuário pode ler um recurso.

O que faço é criar minhas políticas como uma enumeração e, em seguida, percorrer e configurar os requisitos dessa forma ...

services.AddAuthorization(authorizationOptions =>
        {
            foreach (var policyString in Enum.GetNames(typeof(Enumerations.Security.Policy)))
            {
                authorizationOptions.AddPolicy(
                    policyString,
                    authorizationPolicyBuilder => authorizationPolicyBuilder.Requirements.Add(new DefaultAuthorizationRequirement((Enumerations.Security.Policy)Enum.Parse(typeof(Enumerations.Security.Policy), policyWrtString), DateTime.UtcNow)));

      /* Note that thisn does not stop you from 
          configuring policies directly against a username, claims, roles, etc. You can do the usual.
     */
            }
        }); 

A classe DefaultAuthorizationRequirement se parece com ...

public class DefaultAuthorizationRequirement : IAuthorizationRequirement
{
    public Enumerations.Security.Policy Policy {get; set;} //This is a mere enumeration whose code is not shown.
    public DateTime DateTimeOfSetup {get; set;} //Just in case you have to know when the app started up. And you may want to log out a user if their profile was modified after this date-time, etc.
}

public class DefaultAuthorizationHandler : AuthorizationHandler<DefaultAuthorizationRequirement>
{
    private IAServiceToUse _aServiceToUse;

    public DefaultAuthorizationHandler(
        IAServiceToUse aServiceToUse
        )
    {
        _aServiceToUse = aServiceToUse;
    }

    protected async override Task HandleRequirementAsync(AuthorizationHandlerContext context, DefaultAuthorizationRequirement requirement)
    {
        /*Here, you can quickly check a data source or Web API or etc. 
           to know the latest date-time of the user's profile modification...
        */
        if (_aServiceToUse.GetDateTimeOfLatestUserProfileModication > requirement.DateTimeOfSetup)
        {
            context.Fail(); /*Because any modifications to user information, 
            e.g. if the user used another browser or if by Admin modification, 
            the claims of the user in this session cannot be guaranteed to be reliable.
            */
            return;
        }

        bool shouldSucceed = false; //This should first be false, because context.Succeed(...) has to only be called if the requirement specifically succeeds.

        bool shouldFail = false; /*This should first be false, because context.Fail() 
        doesn't have to be called if there's no security breach.
        */

        // You can do anything.
        await doAnythingAsync();

       /*You can get the user's claims... 
          ALSO, note that if you have a way to priorly map users or users with certain claims 
          to particular policies, add those policies as claims of the user for the sake of ease. 
          BUT policies that require dynamic code (e.g. checking for age range) would have to be 
          coded in the switch-case below to determine stuff.
       */

        var claims = context.User.Claims;

        // You can, of course, get the policy that was hit...
        var policy = requirement.Policy

        //You can use a switch case to determine what policy to deal with here...
        switch (policy)
        {
            case Enumerations.Security.Policy.CanReadResource:
                 /*Do stuff with the claims and change the 
                     value of shouldSucceed and/or shouldFail.
                */
                 break;
            case Enumerations.Security.Policy.AnotherPolicy:
                 /*Do stuff with the claims and change the 
                    value of shouldSucceed and/or shouldFail.
                 */
                 break;
                // Other policies too.

            default:
                 throw new NotImplementedException();
        }

        /* Note that the following conditions are 
            so because failure and success in a requirement handler 
            are not mutually exclusive. They demand certainty.
        */

        if (shouldFail)
        {
            context.Fail(); /*Check the docs on this method to 
            see its implications.
            */
        }                

        if (shouldSucceed)
        {
            context.Succeed(requirement); 
        } 
     }
}

Observe que o código acima também pode ativar o pré-mapeamento de um usuário para uma política em seu armazenamento de dados. Portanto, ao compor declarações para o usuário, você basicamente recupera as políticas que foram pré-mapeadas para o usuário direta ou indiretamente (por exemplo, porque o usuário tem um determinado valor de declaração e esse valor de identificação foi identificado e mapeado para uma política, como que ele fornece mapeamento automático para usuários que também possuem esse valor de declaração) e inscreve as políticas como declarações, de modo que, no manipulador de autorização, você pode simplesmente verificar se as declarações do usuário contêm requisito. reivindicações. Isso é para uma maneira estática de atender a um requisito de política, por exemplo, o requisito "Primeiro nome" é de natureza bastante estática. Assim,

[Authorize(Policy = nameof(Enumerations.Security.Policy.ViewRecord))] 

Um requisito dinâmico pode ser a verificação da faixa etária etc., e as políticas que usam esses requisitos não podem ser pré-mapeadas para os usuários.

Um exemplo de verificação dinâmica de declarações de política (por exemplo, para verificar se um usuário tem mais de 18 anos) já está na resposta dada por @blowdart ( https://stackoverflow.com/a/31465227/4974715 ).

PS: Eu digitei isso no meu telefone. Perdoe quaisquer erros de digitação e falta de formatação.

Olumide
fonte