Авторизация пользователя в зависимости от имени действия

У меня много контроллеров со многими действиями. Каждое действие имеет свою собственную роль (имя роли = имя_контроллера.имя).

В предыдущих версиях я мог проверить, может ли текущий пользователь получить какое-либо действие или не использовать "общий" атрибут AuthorizeAttribute:

public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
{
    string currentAction = actionContext.ActionDescriptor.ActionName;
    string currentController = actionContext.ActionDescriptor.ControllerDescriptor.ControllerName;
    Roles = (currentController + "." + currentAction).ToLower();
    base.OnAuthorization(actionContext);
}

С версией asp.net 5 я обнаружил, что мне нужно использовать требования (Как создать пользовательский атрибут AuthorizeAttribute в ASP.NET Core?), Проблема заключается в том, что AuthorizationContext не дает нам информации о действии, к которому пользователь пытается добраться.

Я не хочу добавлять атрибут Authorize для каждого действия, есть ли способ достичь моего требования в новой структуре? (Я предпочитаю избегать использования HttpContext.Current, он плохо вписывается в архитектуру конвейера)

Ответ 1

Вот общий процесс принудительной аутентификации. Ваша ситуация может быть решена полностью на первом шаге, поскольку вы можете добавить заявку на роль, которая украсит ваш

1. аутентифицировать, создав личность для пользователя

Написание промежуточного программного обеспечения и вставка его в конвейер через IApplicationBuilder.UseMiddleware<> заключается в том, как выполняется обычная проверка подлинности. Здесь мы извлекаем любую информацию, которая может понадобиться позже для авторизации, и помещаем ее в ClaimsIdentity. У нас есть HttpContext, поэтому мы можем захватить информацию из заголовка, куки, запрошенный путь и т.д. Вот пример:

public class MyAuthHandler : AuthenticationHandler<MyAuthOptions>
{
   protected override Task<AuthenticationTicket> HandleAuthenticateAsync()
   {
      // grab stuff from the HttpContext
      string authHeader = Request.Headers["Authorization"] ?? "";
      string path = Request.Path.ToString() ?? "";

      // make a MyAuth identity with claims specifying what we'll validate against
      var identity = new ClaimsIdentity(new[] {
         new Claim(ClaimTypes.Authentication, authHeader),
         new Claim(ClaimTypes.Uri, path)
      }, Options.AuthenticationScheme);

      var ticket = new AuthenticationTicket(new ClaimsPrincipal(identity), 
         new AuthenticationProperties(), Options.AuthenticationScheme);
      return Task.FromResult(ticket);
   }
}

public class MyAuthOptions : AuthenticationOptions
{
   public const string Scheme = "MyAuth";
   public MyAuthOptions()
   {
      AuthenticationScheme = Scheme;
      AutomaticAuthentication = true;
   }
}

public class MyAuthMiddleware : AuthenticationMiddleware<MyAuthOptions>
{
   public MyAuthMiddleware(
               RequestDelegate next,
               IDataProtectionProvider dataProtectionProvider,
               ILoggerFactory loggerFactory,
               IUrlEncoder urlEncoder,
               IOptions<MyAuthOptions> options,
               ConfigureOptions<MyAuthOptions> configureOptions)
         : base(next, options, loggerFactory, urlEncoder, configureOptions)
   {
   }

   protected override AuthenticationHandler<MyAuthOptions> CreateHandler()
   {
      return new MyAuthHandler();
   }
}

public static class MyAuthMiddlewareAppBuilderExtensions
{
   public static IApplicationBuilder UseMyAuthAuthentication(this IApplicationBuilder app, string optionsName = "")
   {
      return app.UseMiddleware<MyAuthMiddleware>(
         new ConfigureOptions<MyAuthOptions>(o => new MyAuthOptions()) { Name = optionsName });
   }
}

Чтобы использовать это промежуточное ПО, вставьте это в Startup.Configure до маршрутизации: app.UseMyAuthAuthentication();

2. разрешать, применяя требования к идентификатору

Мы создали идентификатор для пользователя, но нам все равно нужно его применять. Для этого нам нужно написать AuthorizationHandler следующим образом:

  public class MyAuthRequirement : AuthorizationHandler<MyAuthRequirement>, IAuthorizationRequirement
  {
     public override void Handle(AuthorizationContext context, MyAuthRequirement requirement)
     {
        // grab the identity for the MyAuth authentication
        var myAuthIdentities = context.User.Identities
           .Where(x => x.AuthenticationType == MyAuthOptions.Scheme).FirstOrDefault();
        if (myAuthIdentities == null)
        {
           context.Fail();
           return;
        }

        // grab the authentication header and uri types for our identity
        var authHeaderClaim = myAuthIdentities.Claims.Where(x => x.Type == ClaimTypes.Authentication).FirstOrDefault();
        var uriClaim = context.User.Claims.Where(x => x.Type == ClaimTypes.Uri).FirstOrDefault();
        if (uriClaim == null || authHeaderClaim == null)
        {
           context.Fail();
           return;
        }

        // enforce our requirement (evaluate values from the identity/claims)
        if ( /* passes our enforcement test */ )
        {
           context.Succeed(requirement);
        }
        else
        {
           context.Fail();
        }
     }
  }

3. добавьте обработчик требований в качестве политики авторизации

Наше требование аутентификации должно быть добавлено в Startup.ConfigureServices, чтобы его можно было использовать:

// add our policy to the authorization configuration
services.ConfigureAuthorization(auth =>
{
   auth.AddPolicy(MyAuthOptions.Scheme, 
      policy => policy.Requirements.Add(new MyAuthRequirement()));
});

4. используйте политику авторизации

Последний шаг - обеспечить выполнение этого требования для конкретных действий, украсив наше действие или контроллер [Authorize("MyAuth")]. Если у нас много контроллеров, каждый из которых имеет множество действий, требующих принудительного исполнения, тогда мы можем захотеть создать базовый класс и просто украсить этот единственный контроллер.

Ваша более простая ситуация:

Каждое действие имеет свою собственную роль (Role name = ControllerName.actionName > )

Если у вас уже есть все ваши действия, настроенные с помощью [Authorize(Roles = "controllername.actionname")], вам, вероятно, потребуется только часть № 1 выше. Просто добавьте новый Claim(ClaimTypes.Role, "controllername.actionname"), который действителен для конкретного запроса.