我正在使用 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;
}
我希望这个帮助很适合你。我很高兴收到建设性的意见。