ASP.NET 将授权策略设置为一个特定的 AuthenticationScheme

ASP.NET Set Authorization Policy to one particular AuthenticationScheme

简短版本: 我想应用 IAuthorizationRequirement 但仅针对特定用户。或者找到其他方法来解决我的问题。

长版:

我有一个 Angular SPA 应用程序,它在幕后使用 Asp.Net 5.0 API 来处理 CRUD 和业务逻辑。我的问题是 API 的身份验证和授权。

两种 JwtBearer 认证方案

我有一个遗留要求,即在授权 header).因此应用程序可以与其他应用程序共享其数据。

我们最近添加了使用 OAuth2/PKCE 对具有 third-party 的 UI 用户进行身份验证的功能(它曾经是自定义数据库身份验证)。这也作为 Bearer + JWT 格式的授权 header 中的 JWT。但它的格式与我们 home-grown 共享的 secret-based 格式不同。

执行一项政策,即通过一种方案验证的用户必须 IP-filtered

作为一项单独的工作,我希望将名为 IPAddressRequirement just 的策略/IAuthorizationRequirement 应用于共享秘密身份验证。这样只有配置的 IP 才能使用该身份验证。因此,如果经过身份验证的用户携带 Oauth2 JWT,无论他们来自哪里,我们都会让他们进入。如果他们通过了身份验证但具有共享的 secret-signed JWT,我们将检查他们的 IP。就是这样,以防万一我们的共享秘密泄露出去,外面的坏人就没法利用了。

但是,该策略适用于所有用户

但是,IPAddressRequirement 似乎适用于所有身份验证方案。这将导致 UI 通过 Oauth2 验证的用户的 IP 地址被检查和阻止,除非他们是内部的(不好)。

另一个想法:在策略检查中,我在运行时可以使用以下 objects。有什么我可以在其中任何一个中检查以确定使用了哪个 AuthenticationScheme 的东西(当我正在评估我的策略时?)。

代码

这是我设置策略的方式:

            services.AddAuthorization(options =>
            {
                options.AddPolicy(nameof(IPAddressRequirement), policy =>
                {
                    //I thought this line would limit this policy to just the target scheme, but it did not
                    policy.AddAuthenticationSchemes("SharedSecret");
                    policy.RequireAuthenticatedUser();
                    policy.Requirements.Add(new IPAddressRequirement());
                });
            });

我的遗留 SharedSecret 身份验证创建如下:

                var symmKey = new SymmetricSecurityKey( Encoding.UTF8.GetBytes(("--- SECRET ---")) );
                services.AddAuthentication()
                        .AddJwtBearer("SharedSecret", options =>
                        {
                            options.TokenValidationParameters = new TokenValidationParameters
                            {
                                ValidateIssuerSigningKey = true,
                                IssuerSigningKey = symmKey,
                                ValidateIssuer = false,
                                ValidateAudience = false
                            };
                        });

third-party OAuth2认证方案创建如下:

            services.AddAuthentication()
                    .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
                    {
                        options.Authority = "https://example.com";
                        options.TokenValidationParameters = new TokenValidationParameters
                        {
                            ValidateIssuerSigningKey = true,
                            ValidateIssuer = false,
                            ValidateAudience = false
                        };
                    });

我的控制器有这些授权属性:

    [Authorize(AuthenticationSchemes = "SharedSecret", Policy = nameof(IPAddressRequirement))]
    [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]

我也试过将这些都作为一个单独的授权属性,但似乎没有任何影响。

一些补充说明:

    public class IPAddressRequirement : IAuthorizationRequirement
    {
        public IPAddressRequirement()
        {
        }
    }

    public class IPAddressRequirementHandler : AuthorizationHandler<IPAddressRequirement>
    {
        private readonly IHttpContextAccessor _httpContextAccessor;
        private readonly SharedSecretOptions _sharedSecretOptions;

        public IPAddressRequirementHandler(IHttpContextAccessor httpContextAccessor, IOptions<SharedSecretOptions> SharedSecretOptions)
        {
            _httpContextAccessor = httpContextAccessor;
            _sharedSecretOptions = SharedSecretOptions.Value;
        }

        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, IPAddressRequirement requirement)
        {
            string authorization = _httpContextAccessor.HttpContext.Request.Headers[HeaderNames.Authorization];
            // If we are dealing with Bearer authentication, check their IP otherwise let them in
            if (!string.IsNullOrEmpty(authorization) && authorization.StartsWith("Bearer "))
            {
                IPAddress incomingIp = _httpContextAccessor.HttpContext.Connection.RemoteIpAddress;
                //If the client IP address is loopback OR found in the RemoteIPs configuration, let them in.  
                if (IPAddress.IsLoopback(incomingIp)
                        ||
                        _sharedSecretOptions.RemoteIPs
                            .Select(subnet =>
                                IPNetwork.Parse(subnet))
                            .Any(network =>
                                IPNetwork.Contains(network, incomingIp))
                    )
                {
                    context.Succeed(requirement);
                }
                else
                {
                    context.Fail();
                }
            }
            context.Succeed(requirement); //If we failed above for any reason, this succeed doesn't count.  Which is the correct behavior.
            return Task.CompletedTask;
        }
    }

自定义身份验证处理程序

听起来这最适合您的用例,让您最好地控制和了解行为。请参阅我的 example 以了解如何集成它。您仍然可以在处理程序中使用 Microsoft 的 JWT 验证。

控制索赔主体

自定义处理程序的主要原因是以最可扩展的方式启用基于声明的授权,即使在 JWT 中发送的某些方式是遗留的。这包括添加您自己的声明,例如 trust_level=1 或任何对您的用例有意义的声明。请参阅我的 handler class 示例。

API授权

然后您将能够在 API 逻辑中使用 .NET 声明功能,例如基于策略的授权,如 this Curity code example 中所述。这可以使用您的处理程序生成的自定义声明,例如上面的 trust_level 值。

比属性等代码细节更重要的是,您可以将声明注入您的业务逻辑,如 this code,然后您的业务级别授权很容易扩展。

关于索赔的更多信息

理想情况下,重要的声明应该在 JWT 访问令牌中发布,它们可以通过数字方式验证,并且应该可以包含特定于域的声明,尽管这并不总是受支持。有关此主题的更多信息,请参阅 this claims article 以了解声明如何成为现代 API 中的主要授权机制。