短版本:我想应用IAuthorizationRequirement,但仅适用于特定用户。或者找一种不同的方法来解决我的问题。
长版本:
我有一个Angular SPA应用程序,它在后台为CRUD和业务逻辑使用Asp.Net 5.0 API。我的问题是API的身份验证和授权。
两种JwtBearer认证方案
我有一个遗留要求,即使用共享机密(授权头中的Bearer+JWT(使用机密签名))对同一API的(非人工)客户端进行身份验证和授权。因此,应用程序可以与其他应用程序共享其数据。
我们最近添加了使用OAuth2/PKCE向第三方验证UI用户的功能(它曾经是一种自定义的DB验证)。这也是授权标头中的JWT,格式为Bearer+JWT。但这是一种不同于我们本土共享秘密的形式。
强制执行一项策略,即必须对通过一个方案进行身份验证的用户进行IP筛选
作为另一项工作,我希望将名为IPAddressRequirement
的Policy/IAuthorizationRequirement仅应用于共享机密身份验证。因此,只有配置的IP才能使用该身份验证。因此,如果经过身份验证的用户携带Oauth2 JWT进入,无论他们来自哪里,我们都会让他们进入。如果他们通过了身份验证,但有共享的秘密签名JWT,我们会检查他们的IP。所以,为了以防万一,我们共享的秘密泄露出去,一些外部的坏人不能使用它。
但是,该策略适用于所有用户
然而,IPAddressRequirement似乎对所有身份验证方案都起作用。这将导致通过Oauth2进行身份验证的UI用户的IP地址被检查和阻止,除非他们是内部用户(不好)。
- 我可以提前根据身份验证方案过滤策略吗
- 我应该尝试不同的方法吗
另一个想法是:在策略检查中,我在运行时可以使用以下对象。我是否可以在其中任何一个中检查一些内容,以确定使用了哪个AuthenticationScheme(当我评估我的策略时?)。
- IHttpContextAccessor(通过DI)
- AuthorizationHandlerContext(HandleRequirementAsync方法的参数)
代码
以下是我如何设置我的策略:
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
};
});
第三方OAuth2身份验证方案创建如下:
services.AddAuthentication()
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.Authority = "https://example.com";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
ValidateIssuer = false,
ValidateAudience = false
};
});
我的控制器具有以下Authorize属性:
[Authorize(AuthenticationSchemes = "SharedSecret", Policy = nameof(IPAddressRequirement))]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
我也试着把这些都作为一个单独的Authorize属性,似乎没有任何影响。
一些附加说明:
- 验证似乎都能按预期工作
- 我有一个IAsyncAuthorizationFilter,它在此策略之前启动,并按预期工作。由于我的策略具有RequireAuthenticatedUser,我们正在处理那些已经通过此筛选器检查并具有ClaimsPrincipal的用户
- 这是我的IPAddressRequirement/IPAddressRequirementHandler(有点像做他们应该做的事情)。你会看到我正在检查授权标头。我很高兴在这里寻找活动的身份验证方案,并在那里做任何事情。但我不知道该找什么
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;
}
}
自定义认证处理器
听起来这将是最适合您的用例,为您提供对行为的最佳控制和可见性。请参阅我的示例,了解如何将其集成。您仍然可以在处理程序中使用Microsoft的JWT验证。
对索赔委托人的控制
自定义处理程序的主要原因是以最可扩展的方式启用基于声明的授权,即使JWT中的某些发送方式是遗留的。这包括添加您自己的声明,如trust_level=1
或任何对您的用例有意义的声明。有关示例,请参阅我的处理程序类。
API授权
然后,您将能够在API逻辑中使用.NET声明功能,如基于策略的授权,如本Curity代码示例所述。这可以使用处理程序生成的自定义声明,例如上面的trust_level值。
与属性等代码细节相比,更重要的是,您可以将声明注入到您的业务逻辑中,就像在这段代码中一样,这样您的业务级别授权就很容易扩展了。
关于索赔的更多信息
理想情况下,重要的声明应该在JWT访问令牌中发布,在那里它们是可数字验证的,并且应该可以包括特定于域的声明,尽管这并不总是得到支持。有关此主题的更多信息,请参阅这篇声明文章,了解声明如何成为现代API中的主要授权机制。