简体   繁体   English

ASP.NET Core 6:添加具有多个授权策略的多个身份验证方案以及依赖注入

[英]ASP.NET Core 6 : add multiple authentication schemes with multiple authorization policies along with dependency injection

I need to create a framework for coworkers that allows for multiple authentication schemes and correlating authorization policies (since our IDP has multiple allowed approaches), but those schemes require dependency injection because the IDP information is provided from a cloud-based configuration source.我需要为同事创建一个框架,允许多个身份验证方案和关联授权策略(因为我们的 IDP 有多种允许的方法),但这些方案需要依赖注入,因为 IDP 信息是从基于云的配置源提供的。 The authentication is done with a JWT Bearer token in the cases that I'm working with so far.在我目前使用的情况下,身份验证是使用 JWT Bearer 令牌完成的。

I want to stick to the most current practices to be as current as possible.我想坚持最新的做法,尽可能保持最新。 So it appears that I should be using AddAuthentication , AddJwtBearer , and AddAuthorization on my IServiceCollection .所以看来我应该在我的IServiceCollection上使用AddAuthenticationAddJwtBearerAddAuthorization

I am expecting a controller or endpoint can be decorated with the AuthorizeAttribute and it will employ the designated default policy, which will use the designated default authN scheme.我期待 controller 或端点可以用AuthorizeAttribute装饰,它将采用指定的默认策略,该策略将使用指定的默认 authN 方案。 If the attribute is given constructor parameters of Policy = <Some Non Default Policy> or AuthenticationSchemes = <Some other scheme> , it will switch to those instead.如果该属性被赋予Policy = <Some Non Default Policy>AuthenticationSchemes = <Some other scheme>的构造函数参数,它将切换到这些参数。

First, I established that I needed to use dependency injection, so I'm using IConfigureNamedOptions<JwtBearerOptions> .首先,我确定我需要使用依赖注入,所以我使用IConfigureNamedOptions<JwtBearerOptions>

So I created the options class like this for auth code所以我像这样为身份验证代码创建了选项 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);
    }
}

And I then repeated this pattern for a class called ClientCredentialsJwtBearerOptions , but I have this variation然后我为名为 ClientCredentialsJwtBearerOptions 的ClientCredentialsJwtBearerOptions重复了这种模式,但我有这个变化

public void Configure(JwtBearerOptions options) => Configure("ClientCredentials", options);

I register all of this by setting the default authentication scheme, Adding the JWT Bearers by name with an empty delegate, and then I call to configure the options.我通过设置默认身份验证方案来注册所有这些,按名称添加 JWT 承载者和空委托,然后调用配置选项。

Then I assign policies.然后我分配策略。 I may be wrong, but I don't think the policy creation is the issue, but I'll include the code in case it is a problem.我可能错了,但我认为政策创建不是问题,但我会包含代码以防出现问题。

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;
        });

I am using constants for the strings here, but I wrote literal strings for ease of reading the example.我在这里为字符串使用常量,但我编写了文字字符串以便于阅读示例。

When I test this, everything works fine if I leave it exactly like this.当我对此进行测试时,如果我完全保持这种状态,一切都会正常工作。 And it uses the AuthCodeJwtBearerOptions and the request goes through.它使用AuthCodeJwtBearerOptions并且请求通过。

But if I change the Authorize attribute to但是如果我将Authorize属性更改为

[Authorize(Policy = "ClientCredentialsOnly")]

Authentication still uses AuthCodeJwtBearerOptions .身份验证仍然使用AuthCodeJwtBearerOptions I can get it to switch by simply reversing the order of the Configure call我可以通过简单地颠倒Configure调用的顺序来切换它

serviceCollection.ConfigureOptions<AuthCodeJwtBearerOptions>();
serviceCollection.ConfigureOptions<ClientCredentialsJwtBearerOptions>();        

Suggesting to me that it's just using the last one to be registered and it is not respecting the "Named" functionality of named configurations.向我建议它只是使用要注册的最后一个,并且不尊重命名配置的“命名”功能。

And if I change the default policy, nothing changes.如果我更改默认策略,则不会发生任何变化。

I feel like I've got most of the pieces that I need to get this working and I'm just misunderstanding what ConfigureOptions does.我觉得我已经拥有了完成这项工作所需的大部分内容,但我只是误解了ConfigureOptions的作用。

Any help is appreciated.任何帮助表示赞赏。 Thank you.谢谢你。

There were two problems with my approach, and I think I have a good resolution now.我的方法有两个问题,我认为我现在有一个很好的解决方案。

First, with the help of Jeremy Lakeman I was made aware of the reversed usage of IConfigureNamedOptions<JwtBearerOptions> 's Configure methods.首先,在Jeremy Lakeman的帮助下,我了解到IConfigureNamedOptions<JwtBearerOptions>Configure方法的反向使用。 Instead, you perform a name check for the scheme and if it passes, you configure it.相反,您对方案执行名称检查,如果通过,则对其进行配置。

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);    
}

This gets the options to register correctly.这将获得正确注册的选项。 However, the second issue I was having was that if you set a default authentication scheme, it will always run, even when another scheme or policy is requested explicitly但是,我遇到的第二个问题是,如果您设置默认身份验证方案,它将始终运行,即使明确请求另一个方案或策略

I found, however, that if you do not assign a default authentication scheme and only define a default authentication policy, you can have [Authorize] default to the default policy, but [Authorize("ClientCredentialsOnly")] will perform only the scheme and policy for client credentials.但是,我发现,如果您不分配默认身份验证方案而仅定义默认身份验证策略,则可以将[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")!;
   });

So, let's sum up.所以,让我们总结一下。


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);
    }
}

Repeat the above for other schemes that you want to support, switching the _name , token validation parameters, and event logic as needed.对您想要支持的其他方案重复上述操作,根据需要切换_name 、令牌验证参数和事件逻辑。 I did this for "ClientCredentials" for now.我现在为“ClientCredentials”做了这个。

Now wire it up in your pipeline现在将其连接到您的管道中

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")!;
});

Again, huge thanks to Jeremy for the guidance on the options.再次感谢Jeremy对选项的指导。 And also to Marc for correcting the botch job formatting for my first Stack Overflow post.还要感谢Marc为我的第一篇 Stack Overflow 帖子纠正了拙劣的作业格式。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 ASP.NET Core中的多种身份验证方案 - Multiple authentication schemes in ASP.NET Core ASP.NET 5.0 中可选授权有多种 JWT 认证方案 - Optional authorization with multiple JWT authentication schemes in ASP.NET 5.0 在 ASP.NET Core 3.1 中使用多个身份验证方案? - Using multiple authentication schemes in ASP.NET Core 3.1? 依赖注入的ASP.NET Core多重实现 - ASP.NET Core Multiple Implementation for Dependency Injection 依赖注入 ASP.NET Core:注册多个实现 - Dependency Injection ASP.NET Core: Register multiple implementations ASP.NET Core 2 中多个相同类型实例的依赖注入 - Dependency injection of multiple instances of same type in ASP.NET Core 2 ASP.NET core 2.2:当配置了多个身份验证方案时,ChallengeResult 的预期行为是什么? - ASP.NET core 2.2: what is the expected behaviour of ChallengeResult when there are multiple authentication schemes configured? 尝试对 ASP.NET Core 3.1 使用多个身份验证方案时出现异常 - Exceptions when trying to use multiple authentication schemes with ASP.NET Core 3.1 在ASP.NET 4 Web应用程序中支持多种身份验证策略 - Supporting multiple authentication policies in ASP.NET 4 Web application 如果在 Asp.Net Core 中指定了多个策略(例如中间件配置、控制器/操作属性),则使用哪种授权策略? - Which authorization policy is used if multiple policies are specified (e.g. middleware configuration, controller/action attribute,) in Asp.Net Core?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM