Autorizar Atributo com Múltiplas Funções

97

Eu gostaria de adicionar autorização a um controlador, para várias funções de uma vez.

Normalmente seria assim:

[Authorize(Roles = "RoleA,RoleB,RoleC")]
public async Task<ActionResult> Index()
{
}

Mas eu armazenei minhas funções em constantes, uma vez que elas podem mudar ou ser estendidas em algum ponto.

public const RoleA = "RoleA";
public const RoleB = "RoleB";
public const RoleC = "RoleC";

Não posso fazer isso, pois a string deve ser conhecida no momento da compilação:

[Authorize(Roles = string.join(",",RoleA,RoleB,RoleC)]
public async Task<ActionResult> Index()
{
}

Existe uma maneira de contornar o problema?

PODERIA escrever uma const que simplesmente contenha "RoleA, RoleB, RoleC" - mas não gosto de strings mágicas e esta é uma string mágica. Mudar o nome de uma função e esquecer de mudar a string combinada seria um desastre.

Estou usando MVC5. A identidade e a função do ASP.NET são conhecidas em tempo de compilação.

Christian Sauer
fonte
você está usando public const string RoleA = "RoleA"; ou como você escreveu em questão?
Mukesh Modhvadiya

Respostas:

188

Tente criar um atributo de autorização personalizado como este .

public class AuthorizeRolesAttribute : AuthorizeAttribute
{
    public AuthorizeRolesAttribute(params string[] roles) : base()
    {
        Roles = string.Join(",", roles);
    }
}

Assumindo que suas funções serão as mesmas para vários controladores, crie uma classe auxiliar:

public static class Role
{
    public const string Administrator = "Administrator";
    public const string Assistant = "Assistant";
}

Em seguida, use-o assim:

public class MyController : Controller
{
    [AuthorizeRoles(Role.Administrator, Role.Assistant)]
    public ActionResult AdminOrAssistant()
    {                       
        return View();
    }
}
MacGyver
fonte
12
Essa é uma ideia digna de Mac Gyver;)
Christian Sauer
2
Solução muito boa :)
aup
1
Também gosto muito dessa solução, especialmente porque posso deixar minha função ser um enum em vez de uma string. Qual seria um bom namespace e localização na hierarquia do projeto para colocar este atributo de autorização personalizado?
Simon Shine,
4
Não tenho certeza do que está acontecendo aqui, mas isso NÃO me ajudou, qualquer usuário, independente da função, conseguiu acessar o método.
Urielzen
2
Mesmo problema de @Urielzen, mas foi corrigido pela resposta abaixo de Jerry Finegan (usando "System.Web.Mvc.AuthorizeAttribute e NÃO System.Web.Http.AuthorizeAttribute")
RJB
13

Certifique-se de que você está derivando sua classe de atributo customizado System.Web.Mvc.AuthorizeAttributee NÃO System.Web.Http.AuthorizeAttribute.

Eu tive o mesmo problema. Depois que mudei, tudo funcionou.

Você também pode querer adicionar o seguinte à sua classe de atributo personalizado:

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)] 
Jerry Finegan
fonte
Eu apenas tentei isso e encontrei uma referência à biblioteca System.Web.Http.AuthorizeAttributeINSTEAD OFSystem.Web.Mvc.AuthorizeAttribute
fraser jordan
10

A melhor e mais simples maneira que encontrei de resolver esse problema é apenas concatenar funções no atributo Autorizar.

[Authorize(Roles = CustomRoles.Admin + "," + CustomRoles.OtherRole)]

com CustomRole uma classe com strings constantes como esta:

public static class CustomRoles
{
    public const string Admin = "Admin";
    // and so on..
}
ChristopheHvd
fonte
2
Valioso; mas isso deve ser um comentário; não é uma resposta.
GhostCat
1
Solução simples e elegante!
Iosif Bancioiu
A sua resposta e a resposta aceita dispararão a autorização se implementada corretamente (estou usando o aceito em um aplicativo da web de produção). Propor uma edição para remover os comentários sobre a resposta aceita.
Eric Eskildsen
3

O que fiz foi a resposta em @Tieson

Eu ajeito um pouco em sua resposta. Em vez de string.Join, por que não convertê-lo em lista?

Aqui está minha resposta:

public class AuthorizeRolesAttribute : AuthorizeAttribute
{
    private new List<string> Roles;
    public AuthorizeRolesAttribute(params string[] roles) : base()
    {
        Roles = roles.toList()
    }
}

Em seguida, verifique se a função é válida, substituindo OnAuthorization

public override void OnAuthorization(HttpActionContext actionContext)
{
            if (Roles == null)
                HandleUnauthorizedRequest(actionContext);
            else
            {
                ClaimsIdentity claimsIdentity = HttpContext.Current.User.Identity as ClaimsIdentity;
                string _role = claimsIdentity.FindFirst(ClaimTypes.Role).Value;
                bool isAuthorize = Roles.Any(role => role == _role);

                if(!isAuthorize)
                    HandleUnauthorizedRequest(actionContext);
            }
        }

E aí está, agora está validando se a função está autorizada a acessar o recurso

Christopher Enriquez
fonte
1

Acho que um atributo de autorização personalizado é um exagero para esse problema, a menos que você tenha uma grande quantidade de funções.

Visto que a string deve ser conhecida no momento da compilação, por que não criar uma classe Role estática que contenha strings públicas das funções que você definiu e, em seguida, adicionar strings separadas por vírgulas com certas funções que você deseja autorizar:

public static class Roles
{
    public const string ADMIN = "Admin";
    public const string VIEWER = "Viewer";

    public const string ADMIN_OR_VIEWER = ADMIN + "," + VIEWER;
}

E então você pode usar o Atributo de Autorização da mesma forma que na Classe do Controlador ou no Método do Controlador (ou ambos):

[Authorize(Roles = Roles.ADMIN]
public class ExampleController : Controller
{
    [Authorize(Roles = Roles.ADMIN_OR_VIEWER)
    public ActionResult Create()
    {
        ..code here...
    }
}
Robert Tuttle
fonte
1
Este exemplo não funciona, ou pelo menos não da maneira que você imagina. Por exemplo, embora seja novo, o ADMIN_OR_VIEWERpapel na ação é redundante porque você não terá permissão para acessar o Createmétodo se ainda não tiver o ADMINpapel. Nesse caso VIEWER, nunca será possível invocar o Createmétodo.
John Leidegren,
Esta solução também não é escalonável. Haverá um ponto em que você terá muitas funções com ações diferentes e não
deverá