Атрибут авторизации с несколькими ролями

100

Я хочу добавить авторизацию к контроллеру сразу для нескольких ролей.

Обычно это выглядит так:

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

Но я сохранил свои роли в константах, поскольку они могут измениться или быть расширены в какой-то момент.

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

Я не могу этого сделать, так как строка должна быть известна во время компиляции:

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

Есть ли способ обойти проблему?

Я МОГ написать константу, которая просто содержит «RoleA, RoleB, RoleC» - но мне не нравятся волшебные строки, а это волшебная строка. Изменение имени роли и забывание изменить объединенную строку было бы катастрофой.

Я использую MVC5. Идентификация ASP.NET и роль известны во время компиляции.

Кристиан Зауэр
источник
вы используете публичную константную строку RoleA = "RoleA"; или как вы написали в вопросе?
Мукеш Модхвадия

Ответы:

196

Попробуйте создать собственный атрибут авторизации, подобный этому .

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

Предполагая, что ваши роли будут одинаковыми для нескольких контроллеров, создайте вспомогательный класс:

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

Тогда используйте это так:

public class MyController : Controller
{
    [AuthorizeRoles(Role.Administrator, Role.Assistant)]
    public ActionResult AdminOrAssistant()
    {                       
        return View();
    }
}
МакГайвер
источник
12
Это идея, достойная Мака Гайвера;)
Christian Sauer
2
Очень красивое решение :)
aup
1
Мне также очень нравится это решение, особенно потому, что я могу позволить моей роли быть перечислением, а не строкой. Каким будет хорошее пространство имен и расположение в иерархии проекта для размещения этого настраиваемого атрибута авторизации?
Саймон Шайн
4
Я не уверен, что здесь происходит, но это НЕ помогло мне, любой пользователь независимо от роли мог получить доступ к методу.
Urielzen 07
2
Тот же вопрос , как @Urielzen, но это было зафиксировано в ответ ниже от Джерри Финегана ( с использованием «System.Web.Mvc.AuthorizeAttribute и NOT System.Web.Http.AuthorizeAttribute»)
RJB
13

Убедитесь, что вы производите свой класс настраиваемого атрибута, System.Web.Mvc.AuthorizeAttributeа НЕ System.Web.Http.AuthorizeAttribute.

Я столкнулся с той же проблемой. Как только я его поменял, все заработало.

Вы также можете добавить в свой класс настраиваемых атрибутов следующее:

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)] 
Джерри Финеган
источник
Я просто попробовал это и нашел ссылку на библиотеку System.Web.Http.AuthorizeAttributeВМЕСТОSystem.Web.Mvc.AuthorizeAttribute
fraser jordan
13

Лучший и самый простой способ решить эту проблему - просто объединить роли в атрибуте Authorize.

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

с CustomRole - класс с такими постоянными строками:

public static class CustomRoles
{
    public const string Admin = "Admin";
    // and so on..
}
ChristopheHvd
источник
2
Ценный; но это должен быть комментарий; не ответ.
GhostCat 01
1
Простое и элегантное решение!
Iosif Bancioiu
И ваш ответ, и принятый ответ вызовут авторизацию, если он реализован правильно (я использую принятый в производственном веб-приложении). Предложение редактирования для удаления комментариев к принятому ответу.
Эрик Эскилдсен
Если роли были перечислениями, то вы можете использовать что-то вроде [Authorize (Roles = nameof (UserRoleEnum.User) + "," + nameof (UserRoleEnum.Admin))]
Варун
3

То, что я сделал, - это ответ в @Tieson

Я немного подправил его ответ. Вместо string.Join, почему бы не преобразовать его в список?

Вот мой ответ:

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

А затем проверьте, действительна ли роль, переопределяя 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);
            }
        }

Вот и все, теперь он проверяет, авторизована ли роль для доступа к ресурсу.

Кристофер Энрикес
источник
1

Мне кажется, что настраиваемый атрибут авторизации - излишний для этой проблемы, если у вас нет большого количества ролей.

Поскольку строка должна быть известна во время компиляции, почему бы не создать статический класс Role, содержащий общедоступные строки ролей, которые вы определили, а затем добавить строки, разделенные запятыми, с определенными ролями, которые вы хотите авторизовать:

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

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

И затем вы можете использовать атрибут авторизации, например, в классе контроллера или методе контроллера (или обоих):

[Authorize(Roles = Roles.ADMIN]
public class ExampleController : Controller
{
    [Authorize(Roles = Roles.ADMIN_OR_VIEWER)
    public ActionResult Create()
    {
        ..code here...
    }
}
Роберт Таттл
источник
1
Этот пример не работает, по крайней мере, не так, как вы думаете. Например, в романе ADMIN_OR_VIEWERроль в действии является избыточной, потому что вам не будет разрешено получить доступ к Createметоду, если у вас еще нет ADMINроли. В этом случае VIEWERникогда не удастся вызвать Createметод.
Джон Лейдегрен 05
Это решение тоже не масштабируется.
Наступит