1

我用和编写了一个身份验证web api项目。DotNet core 3.1Microsoft.IdentityModel.JsonWebTokens

我有 3 个客户app-androidapp-pwa并且admin-panel. 每个客户都有特定的SigningCredentialsEncryptingCredentials关键的数据库。

当我想生成访问令牌时,我使用这些客户端之一。

var token = new SecurityTokenDescriptor()
        {
            Audience = client.Audience,
            Claims = claims,
            Expires = DateTimeOffset.UtcNow.Add(client.AccessTokenLifeTime).DateTime,
            Issuer = client.Issuer.GetDisplayName(),
            CompressionAlgorithm = client.SupportCompression ? CompressionAlgorithms.Deflate : null,
            IssuedAt = DateTime.UtcNow,
            NotBefore = DateTime.UtcNow,
            EncryptingCredentials =
                new EncryptingCredentials(
                    new SymmetricSecurityKey(
                        Encoding.UTF8.GetBytes(client.EncryptingKey)),
                    JwtConstants.DirectKeyUseAlg, SecurityAlgorithms.Aes256CbcHmacSha512),
            SigningCredentials =
                new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(client.SigningKey)),
                    SecurityAlgorithms.HmacSha256Signature),
        };

Startup.cs文件中我需要设置AddJwtBearer选项

AddJwtBearer(x => x.TokenValidationParameters = new TokenValidationParameters()
        {
            ValidateIssuer = false,
            ValidateAudience = false,
            TokenDecryptionKey =
                new SymmetricSecurityKey(
                    Encoding.ASCII.GetBytes("encrypt_key")),
             IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("security_key"))
        })

但我需要为每个客户端动态设置这些键

我怎样才能做到这一点?

谢谢

4

1 回答 1

1

我正在使用 Net Core 5,并且我实现了一些可以指导您的代码。请耐心等待,因为这个例子有很多代码。

我在数据库中存储了 ApplicationCompany 表中的 TokenValidationParameter 信息,在我的示例中:

[Index(nameof(AuthenticationScheme), IsUnique = true)]
[Table("application_company")]
public class ApplicationCompany
{
    [Key, Column("id")]
    public int Id { get; set; }

    [Column("authentication_schema")]
    public string AuthenticationScheme { get; set; }

    [Column("company_id"), ForeignKey("Company")]
    public int CompanyId { get; set; }

    [Column("application_id"), ForeignKey("Application")]
    public int ApplicationId { get; set; }

    [Column("environment_id"), ForeignKey("Environment")]
    public int EnvironmentId { get; set; }

    [Column("valid_audience")]
    public string ValidAudience { get; set; }

    [Column("valid_issuer")]
    public string ValidIssuer { get; set; }

    [Column("access_token_secret")]
    public string AccessTokenSecret { get; set; }

    [Column("refresh_token_secret")]
    public string RefreshTokenSecret { get; set; }

    [Column("access_token_expiration")]
    public int AccessTokenExpirationMinutes { get; set; }

    [Column("refresh_token_expiration")]
    public int RefreshTokenExpirationMinutes { get; set; }

    public virtual Company Company { get; set; }

    public virtual Application Application { get; set; }

    public virtual Environment Environment { get; set; }
}

请记住,这篇文章的目的不是解释我的数据库 shema,也不是我如何实现工作单元和 DBContext 管理。

然后,在 Startup.cs 中,在 ConfigureServices() 中,我在 applicationCompanies 变量中获取了所有配置信息。之后,我使用默认选项配置了“MyScheme”方案。

   public void ConfigureServices(IServiceCollection services)
    {
        ...    

        // If you don't register IHttpContextAccessor explicitly before GetConfiguredApplications(), your DbContext class 
        // will have optionsBuilder.IsConfigured = false at OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        // Reference: https://stackoverflow.com/questions/38338475/no-database-provider-has-been-configured-for-this-dbcontext-on-signinmanager-p
        services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

        // Get ApplicationCompany list from Postgres database
        var applicationCompanies = GetConfiguredApplications(services);

        // Add Jwt Bearer
        var authBuilder = services.AddAuthentication(options =>
        {
            options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
        })
        .AddJwtBearer("MyScheme", options =>
        {
            var key = Encoding.UTF8.GetBytes(Configuration["JwtConfig:AccessTokenSecret"]);

            options.SaveToken = true;
            options.RequireHttpsMetadata = true;
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuerSigningKey = true, // this will validate the 3rd part of the jwt token using the secret that we added in the appsettings and verify we have generated the jwt token
                IssuerSigningKey = new SymmetricSecurityKey(key), // Add the secret key to our Jwt encryption
                ValidateIssuer = true, // The issuer of the token
                ValidateAudience = true, // The audience of the token
                ValidAudiences = Configuration["JWTConfig:ValidAudience"].Split(";"),
                ValidIssuer = Configuration["JWTConfig:ValidIssuer"],
                RequireExpirationTime = true,
                ValidateLifetime = true,
                ClockSkew = TimeSpan.Zero
            };
        });

        ...

在 ConfigureServices() 中配置了我的默认 JwtBearer 处理后,我迭代了我的 applicationCompanies 列表并为我的 Jwt 处理添加了更多方案:

        // Add other JWT Schemas
        foreach (var applicationCompany in applicationCompanies)
        {
            authBuilder.
                AddJwtBearer(applicationCompany.AuthenticationScheme, options =>
                {
                    var key = Encoding.UTF8.GetBytes(applicationCompany.AccessTokenSecret);

                    options.SaveToken = true;
                    options.RequireHttpsMetadata = true; 
                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateIssuerSigningKey = true, // this will validate the 3rd part of the jwt token using the secret that we added in the appsettings and verify we have generated the jwt token
                        IssuerSigningKey = new SymmetricSecurityKey(key), // Add the secret key to our Jwt encryption
                        ValidateIssuer = true, // The issuer of the token
                        ValidateAudience = true, // The audience of the token
                        ValidAudiences = applicationCompany.ValidAudience.Split(";"),
                        ValidIssuer = Configuration["JWTConfig:ValidIssuer"],
                        RequireExpirationTime = true,
                        ValidateLifetime = true,
                        ClockSkew = TimeSpan.Zero
                    };
                });
        }
           // Consider ApplicationCompany modifications
        AddMultiSchemeJwtBearerAuthentication(services, Configuration, authBuilder);

然后,在 AddMultiSchemeJwtBearerAuthentication() 中,我为默认身份验证方案提供了一个“选择器”策略,我根据接收到的令牌决定应用什么模式。在同样的处理中,我在使用 services.PostConfigure 时动态设置了 AddJwtBearer 选项。

    private static void AddMultiSchemeJwtBearerAuthentication(IServiceCollection services, IConfiguration Configuration, AuthenticationBuilder authenticationBuilder)
    {
        // Add scheme selector.
        authenticationBuilder.AddPolicyScheme(
            JwtBearerDefaults.AuthenticationScheme,
            "Selector",
            options =>
            {
                options.ForwardDefaultSelector = context =>
                {
                    string token = JwtUtils.GetToken(context);

                    if (!String.IsNullOrEmpty(token))
                    {
                        var jwtHandler = new JwtSecurityTokenHandler();

                        var decodedToken = jwtHandler.ReadJwtToken(token);

                        List<Claim> claimList = decodedToken?.Claims?.ToList();

                        if (claimList != null)
                        {
                            string scheme = claimList.Find(x => x.Type.Equals("scheme"))?.Value;

                            var applicationCompany = Startup.GetConfiguredApplicationByScheme(services, scheme);

                            if (applicationCompany != null)
                            {
                                // In order to update dinamically JWT configuration according to database
                                services.PostConfigure<JwtBearerOptions>(scheme, options =>
                                {
                                    var key = Encoding.UTF8.GetBytes(applicationCompany.AccessTokenSecret);

                                    options.TokenValidationParameters = new TokenValidationParameters
                                    {
                                        ValidateIssuerSigningKey = true, // this will validate the 3rd part of the jwt token using the secret that we added in the appsettings and verify we have generated the jwt token
                                        IssuerSigningKey = new SymmetricSecurityKey(key), // Add the secret key to our Jwt encryption
                                        ValidateIssuer = true, // The issuer of the token
                                        ValidateAudience = true, // The audience of the token
                                        ValidAudiences = applicationCompany.ValidAudience.Split(";"),
                                        ValidIssuer = Configuration["JWTConfig:ValidIssuer"],
                                        RequireExpirationTime = true,
                                        ValidateLifetime = true,
                                        ClockSkew = TimeSpan.Zero
                                    };
                                });
                                return scheme;
                            }
                        }
                    }
                    return "MyScheme";
                };
            }
        );
    }

在我的 Login() 方法中,我使用生成的令牌来存储具有 ClaimType“方案”的声明。我使用此声明类型来决定在 Startup.cs 的 AddMultiSchemeJwtBearerAuthentication() 方法中使用什么方案。

   authClaims.Add(new Claim("scheme", applicationCompany.AuthenticationScheme));

另外,我给你我获取令牌的方法:

    public static string GetToken(HttpContext context)
    {
        string authHeader = context.Request.Headers["Authorization"];
        string token = null;

        if (authHeader != null && authHeader.Contains("Bearer "))
        {
            token = authHeader[7..];
        }

        return token;
    }

以及我用来从数据库中获取配置的 ApplicationCompany 列表的方式:

    private static List<ApplicationCompany> GetConfiguredApplications(IServiceCollection services)
    {
        List<ApplicationCompany> result = new List<ApplicationCompany>();

        try
        {
            var sp = services.BuildServiceProvider();

            var unitOfWork = sp.GetRequiredService<IUnitOfWork>();

            var task = Task.Run(async () => await unitOfWork.ApplicationCompanies.GetAllAsync());
            if (task.IsFaulted && task.Exception != null)
            {
                throw task.Exception;
            }
            result = task.Result as List<ApplicationCompany>;
        }
        catch (Exception)
        {
            Console.WriteLine("Cannot build Application Company list");
        }

        return result;
    }

我希望这个帮助很适合你。我很高兴收到建设性的意见。

于 2021-08-26T20:07:11.180 回答