繁体   English   中英

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

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

我需要为同事创建一个框架,允许多个身份验证方案和关联授权策略(因为我们的 IDP 有多种允许的方法),但这些方案需要依赖注入,因为 IDP 信息是从基于云的配置源提供的。 在我目前使用的情况下,身份验证是使用 JWT Bearer 令牌完成的。

我想坚持最新的做法,尽可能保持最新。 所以看来我应该在我的IServiceCollection上使用AddAuthenticationAddJwtBearerAddAuthorization

我期待 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM