[英]ASP.NET Core 6 : add multiple authentication schemes with multiple authorization policies along with dependency injection
我需要为同事创建一个框架,允许多个身份验证方案和关联授权策略(因为我们的 IDP 有多种允许的方法),但这些方案需要依赖注入,因为 IDP 信息是从基于云的配置源提供的。 在我目前使用的情况下,身份验证是使用 JWT Bearer 令牌完成的。
我想坚持最新的做法,尽可能保持最新。 所以看来我应该在我的IServiceCollection
上使用AddAuthentication
、 AddJwtBearer
和AddAuthorization
。
我期待 controller 或端点可以用AuthorizeAttribute
装饰,它将采用指定的默认策略,该策略将使用指定的默认 authN 方案。 如果该属性被赋予Policy = <Some Non Default Policy>
或AuthenticationSchemes = <Some other scheme>
的构造函数参数,它将切换到这些参数。
首先,我确定我需要使用依赖注入,所以我使用IConfigureNamedOptions<JwtBearerOptions>
。
所以我像这样为身份验证代码创建了选项 class
public class AuthCodeJwtBearerOptions : IConfigureNamedOptions<JwtBearerOptions>
{
private readonly IClaimsTransformation _claimsTransformation;
private readonly ConfigurationManager<OpenIdConnectConfiguration> _configurationManager;
public AuthCodeJwtBearerOptions(IOptions<TidV4OAuthSettings> tidV4OAuthOptions,
IClaimsTransformation claimsTransformation)
{
_claimsTransformation = claimsTransformation;
_configurationManager =
new ConfigurationManager<OpenIdConnectConfiguration>(tidV4OAuthOptions.Value.WellknownUrl,
new OpenIdConnectConfigurationRetriever());
}
public void Configure(JwtBearerOptions options) => Configure("AuthCode", options);
public void Configure(string name, JwtBearerOptions options)
{
var task = Task.Run(async () => await GetTokenValidationParametersAsync());
options.TokenValidationParameters = task.Result;
options.Events = new JwtBearerEvents { OnTokenValidated = OnTokenValidated };
}
private async Task<TokenValidationParameters> GetTokenValidationParametersAsync()
{
var cancellationToken = new CancellationToken();
var openIdConnectConfiguration = await _configurationManager.GetConfigurationAsync(cancellationToken);
return new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKeys = openIdConnectConfiguration?.SigningKeys,
ValidateAudience = false,
ValidateIssuer = true,
ValidIssuer = openIdConnectConfiguration?.Issuer,
ValidateLifetime = true
};
}
private async Task OnTokenValidated(TokenValidatedContext context)
{
if (context.Principal == null)
{
return;
}
context.Principal = await _claimsTransformation.TransformAsync(context.Principal);
}
}
然后我为名为 ClientCredentialsJwtBearerOptions 的ClientCredentialsJwtBearerOptions
重复了这种模式,但我有这个变化
public void Configure(JwtBearerOptions options) => Configure("ClientCredentials", options);
我通过设置默认身份验证方案来注册所有这些,按名称添加 JWT 承载者和空委托,然后调用配置选项。
然后我分配策略。 我可能错了,但我认为政策创建不是问题,但我会包含代码以防出现问题。
serviceCollection
.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = "AuthCode";
options.DefaultChallengeScheme = "AuthCode";
})
.AddJwtBearer("AuthCode", _ => { })
.AddJwtBearer("ClientCredentials", _ => { });
serviceCollection.ConfigureOptions<ClientCredentialsJwtBearerOptions>();
serviceCollection.ConfigureOptions<AuthCodeJwtBearerOptions>();
serviceCollection.AddAuthorization(options =>
{
var authCodePolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddAuthenticationSchemes("AuthCode")
.Build();
var clientCredentialsPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddAuthenticationSchemes("ClientCredentials")
.Build();
var allPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddAuthenticationSchemes("AuthCode", "ClientCredentials")
.Build();
options.AddPolicy("AuthCodeOnly", authCodePolicy);
options.AddPolicy("ClientCredentialsOnly", clientCredentialsPolicy);
options.AddPolicy( "AllPolicies", allPolicy);
options.DefaultPolicy = authCodePolicy;
});
我在这里为字符串使用常量,但我编写了文字字符串以便于阅读示例。
当我对此进行测试时,如果我完全保持这种状态,一切都会正常工作。 它使用AuthCodeJwtBearerOptions
并且请求通过。
但是如果我将Authorize
属性更改为
[Authorize(Policy = "ClientCredentialsOnly")]
身份验证仍然使用AuthCodeJwtBearerOptions
。 我可以通过简单地颠倒Configure
调用的顺序来切换它
serviceCollection.ConfigureOptions<AuthCodeJwtBearerOptions>();
serviceCollection.ConfigureOptions<ClientCredentialsJwtBearerOptions>();
向我建议它只是使用要注册的最后一个,并且不尊重命名配置的“命名”功能。
如果我更改默认策略,则不会发生任何变化。
我觉得我已经拥有了完成这项工作所需的大部分内容,但我只是误解了ConfigureOptions
的作用。
任何帮助表示赞赏。 谢谢你。
我的方法有两个问题,我认为我现在有一个很好的解决方案。
首先,在Jeremy Lakeman的帮助下,我了解到IConfigureNamedOptions<JwtBearerOptions>
的Configure
方法的反向使用。 相反,您对方案执行名称检查,如果通过,则对其进行配置。
public void Configure(JwtBearerOptions options)
{
var task = Task.Run(async () => await GetTokenValidationParametersAsync());
options.TokenValidationParameters = task.Result;
options.Events = new JwtBearerEvents { OnTokenValidated = OnTokenValidated };
}
public void Configure(string name, JwtBearerOptions options)
{
if(!name.Equals("AuthCode"))
{
return
}
Configure(options);
}
这将获得正确注册的选项。 但是,我遇到的第二个问题是,如果您设置默认身份验证方案,它将始终运行,即使明确请求另一个方案或策略
但是,我发现,如果您不分配默认身份验证方案而仅定义默认身份验证策略,则可以将[Authorize]
默认设置为默认策略,但[Authorize("ClientCredentialsOnly")]
将仅执行该方案和客户端凭据的策略。
serviceCollection.AddAuthentication()
.AddJwtBearer("AuthCode", _ => { })
.AddJwtBearer("ClientCredentials", _ => { });
serviceCollection.AddAuthorization(options =>
{
var authCodePolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddAuthenticationSchemes("AuthCode")
.Build();
var clientCredentialsPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddAuthenticationSchemes("ClientCredentials")
.Build();
var allPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddAuthenticationSchemes("AuthCode", "ClientCredentials")
.Build();
options.AddPolicy("AuthCodeOnly", authCodePolicy);
options.AddPolicy("ClientCredentialsOnly", clientCredentialsPolicy);
options.AddPolicy( "AllPolicies", allPolicy);
options.DefaultPolicy = options.GetPolicy("AuthCodeOnly")!;
});
所以,让我们总结一下。
public class AuthCodeJwtBearerOptions : IConfigureNamedOptions<JwtBearerOptions>
{
private readonly IClaimsTransformation _claimsTransformation;
private readonly ConfigurationManager<OpenIdConnectConfiguration> _configurationManager;
private readonly string _name;
public AuthCodeJwtBearerOptions(IOptions<TidV4OAuthSettings> tidV4OAuthOptions,
IClaimsTransformation claimsTransformation)
{
_name = "AuthCode";
_claimsTransformation = claimsTransformation;
_configurationManager =
new ConfigurationManager<OpenIdConnectConfiguration>(tidV4OAuthOptions.Value.WellknownUrl,
new OpenIdConnectConfigurationRetriever());
}
public void Configure(JwtBearerOptions options)
{
var task = Task.Run(async () => await GetTokenValidationParametersAsync());
options.TokenValidationParameters = task.Result;
options.Events = new JwtBearerEvents { OnTokenValidated = OnTokenValidated };
}
public void Configure(string name, JwtBearerOptions options)
{
if(!name.Equals(_name))
{
return;
}
Configure(options);
}
private async Task<TokenValidationParameters> GetTokenValidationParametersAsync()
{
var cancellationToken = new CancellationToken();
var openIdConnectConfiguration = await _configurationManager.GetConfigurationAsync(cancellationToken);
return new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKeys = openIdConnectConfiguration?.SigningKeys,
ValidateAudience = false,
ValidateIssuer = true,
ValidIssuer = openIdConnectConfiguration?.Issuer,
ValidateLifetime = true
};
}
private async Task OnTokenValidated(TokenValidatedContext context)
{
if (context.Principal == null)
{
return;
}
//whatever you need to do once validated including claims transformation
context.Principal = await _claimsTransformation.TransformAsync(context.Principal);
}
}
对您想要支持的其他方案重复上述操作,根据需要切换_name
、令牌验证参数和事件逻辑。 我现在为“ClientCredentials”做了这个。
现在将其连接到您的管道中
serviceCollection.AddAuthentication()
.AddJwtBearer("AuthCode", _ => { })
.AddJwtBearer("ClientCredentials", _ => { });
serviceCollection.ConfigureOptions<ClientCredentialsJwtBearerOptions>();
serviceCollection.ConfigureOptions<AuthCodeJwtBearerOptions>();
serviceCollection.AddAuthorization(options =>
{
var authCodePolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddAuthenticationSchemes("AuthCode")
.Build();
var clientCredentialsPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddAuthenticationSchemes("ClientCredentials")
.Build();
var allPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddAuthenticationSchemes("AuthCode", "ClientCredentials")
.Build();
options.AddPolicy("AuthCodeOnly", authCodePolicy);
options.AddPolicy("ClientCredentialsOnly", clientCredentialsPolicy);
options.AddPolicy( "AllPolicies", allPolicy);
options.DefaultPolicy = options.GetPolicy("AuthCodeOnly")!;
});
再次感谢Jeremy对选项的指导。 还要感谢Marc为我的第一篇 Stack Overflow 帖子纠正了拙劣的作业格式。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.