【问题标题】:Authorizing a user depending on the action name根据操作名称授权用户
【发布时间】:2015-07-28 22:06:53
【问题描述】:

我有很多控制器,有很多动作。每个动作都有自己的角色(角色名称 = ControllerName.actionName)。

在以前的版本中,我可以测试当前用户是否可以使用“通用” 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 版本,我发现我需要使用需求 (How do you create a custom AuthorizeAttribute in ASP.NET Core?)。问题是 AuthorizationContext 没有向我们提供有关用户尝试执行的操作的信息。

我不想在每个操作上都添加 Authorize 属性,有什么方法可以通过新框架实现我的要求? (我更喜欢避免使用 HttpContext.Current,它不太适合管道架构)

【问题讨论】:

    标签: c# asp.net asp.net-core asp.net-core-mvc


    【解决方案1】:

    这是强制执行自定义身份验证的一般过程。您的情况可能可以在第一步中完全解决,因为您可以为装饰您的角色添加声明

    1. 验证为用户创建一个身份

    编写中间件并通过IApplicationBuilder.UseMiddleware<> 将其插入管道是完成自定义身份验证的方式。这是我们提取授权以后可能需要的任何信息的地方,并将其放入ClaimsIdentity。我们在这里有一个HttpContext,因此我们可以从标头、cookie、请求的路径等中获取信息。这是一个示例:

    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")] 装饰我们的操作或控制器来强制执行对特定操作的此要求。如果我们有许多控制器,每个控制器都有许多需要强制执行的操作,那么我们可能想要创建一个基类并仅装饰单个控制器。

    你更简单的情况:

    每个动作都有自己的角色(角色名称 = ControllerName.actionName>)

    如果您已经使用[Authorize(Roles = "controllername.actionname")] 对所有操作进行了微调,那么您可能只需要上面的第 1 部分。只需添加一个对特定请求有效的新 Claim(ClaimTypes.Role, "controllername.actionname")。

    【讨论】:

    • 感谢您的回答,但我想在管道的早期保留授权,actionFilter 来得很晚,仅授权会很昂贵。
    • 您的 HTTP 标头或 cookie 是否(或可以)包含可用于识别角色的授权信息?如果您仅依赖于指定的操作,那么您需要完成 MVC 路由(由于默认值),然后才能知道将执行什么操作
    • 我的header只包含一个token,所以如果授权在路由之前,我更喜欢自己解析请求url,不会很复杂
    • @pinpoint 向我提到,第 1 部分是编写中间件的通用方法,但有一些特定于授权的类提供其他好处。不幸的是,不同的 beta 版本之间存在显着的 API 差异。看看asp.net security repo 中的AuthenticationMiddleware 和AuthenticationHandler。
    • 看起来 AuthenticationTicket 是旧的,请参阅stackoverflow.com/a/37415902/632495
    猜你喜欢
    • 2014-06-25
    • 2022-08-03
    • 2020-12-23
    • 1970-01-01
    • 2011-05-28
    • 2017-08-05
    • 1970-01-01
    • 1970-01-01
    • 2021-06-07
    相关资源
    最近更新 更多