我需要为同事创建一个框架,允许多种身份验证方案和相关的授权策略(因为我们的IDP有多种允许的方法(,但这些方案需要依赖注入,因为IDP信息是从基于云的配置源提供的。在我目前正在处理的情况下,身份验证是使用JWT Bearer令牌完成的。
我想坚持最新的做法,尽可能做到最新。因此,我似乎应该在IServiceCollection
上使用AddAuthentication
、AddJwtBearer
和AddAuthorization
。
我希望控制器或端点可以用AuthorizeAttribute
进行装饰,并且它将使用指定的默认策略,该策略将使用指定默认authN方案。如果属性被赋予Policy = <Some Non Default Policy>
或AuthenticationSchemes = <Some other scheme>
的构造函数参数,它将切换到这些参数。
首先,我确定我需要使用依赖项注入,所以我使用IConfigureNamedOptions<JwtBearerOptions>
。
所以我为身份验证代码创建了这样的选项类
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
的类重复了这个模式,但我有这个变体
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帖子纠正了糟糕的作业格式。