如何正确设置 JwtBearerOptions
How to properly setup JwtBearerOptions
我设置 Identity Server 4 来发布 JWT 令牌来验证用户。在 Identity Server 4 中,我设置了以下内容:
public class Resources {
public static IEnumerable<ApiResource> GetResources() {
return new[] {
new ApiResource {
Name = "Test.API",
DisplayName = "Test API",
Description = "Allow the user access to the test API",
Scopes = new List<string> { "Core API" },
UserClaims = new List<string> { "General", "Admin" }
}
};
}
public static IEnumerable<ApiScope> GetScopes() {
return new[] {
new ApiScope("Core.API", "Allow access to the test API")
};
}
public static IEnumerable<Client> GetClients() {
return new List<Client>() {
new Client {
ClientName = "Test Client",
ClientId = "b778a2ad-090d-4525-8954-6411de2cd339",
ClientSecrets = new List<Secret> { new Secret("random_text".Sha512()) },
AllowedScopes = new List<string> { "Core.API" },
AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials,
},
new Client {
ClientName = "Test Web App",
ClientId = "abb9c89c-a018-4b0f-9a0f-4e701c637665",
ClientSecrets = new List<Secret> { new Secret("other_random_text".Sha512()) },
AllowedGrantTypes = GrantTypes.Hybrid,
RequirePkce = false,
AllowRememberConsent = false,
AllowedScopes = new List<string>
{
StandardScopes.OpenId,
StandardScopes.Profile,
StandardScopes.Address,
StandardScopes.Email,
"Core.API",
"roles"
}
}
};
}
public static IEnumerable<IdentityResource> GetIdentities() {
return new[] {
new IdentityResources.Email(),
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResource {
Name = "User Role",
UserClaims = new List<string> { "Admin", "General" }
}
};
}
}
public class Startup {
public Startup(IConfiguration configuration, IWebHostEnvironment appEnv) {
Configuration = configuration;
CurrentEnvironment = appEnv;
}
public IConfiguration Configuration { get; }
private IWebHostEnvironment CurrentEnvironment { get; set; }
public void ConfigureServices(IServiceCollection services) {
services.AddScoped<IUserRequester, UserRequester>(_ =>
new UserRequester(Configuration.GetSection("AzureTableStore.UserLogin").Get<TableStoreConfiguration>()));
services.AddControllers().AddNewtonsoftJson(options =>
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver());
IIdentityServerBuilder builder = services.AddIdentityServer();
if (CurrentEnvironment.IsDevelopment()) {
builder.AddDeveloperSigningCredential();
} else {
X509Certificate2 certData = DownloadCertificate(Configuration.GetSection("APICertificate").Get<Secret>());
builder.AddSigningCredential(certData);
}
builder.AddInMemoryClients(Resources.GetClients());
builder.AddInMemoryIdentityResources(Resources.GetIdentities());
builder.AddInMemoryApiResources(Resources.GetResources());
builder.AddInMemoryApiScopes(Resources.GetScopes());
builder.Services.Configure<TableStoreConfiguration>(Configuration.GetSection("AzureTableStore.UserLogin"));
builder.Services.Configure<RedisConfiguration>(Configuration.GetSection("RedisCache"));
builder.Services.AddTransient<IRedisConnection, RedisConnection>();
builder.Services.AddTransient<IUserRequester, UserRequester>();
builder.Services.AddTransient<IProfileService, ProfileService>();
builder.Services.AddTransient<IResourceOwnerPasswordValidator, PasswordValidator>();
builder.Services.AddTransient<IAuthorizationCodeStore, AuthorizationCodeStore>();
builder.Services.AddTransient<IReferenceTokenStore, ReferenceTokenStore>();
builder.Services.AddTransient<IRefreshTokenStore, RefreshTokenStore>();
builder.Services.AddTransient<IUserConsentStore, UserConsentStore>();
services.AddSwaggerGen(c => {
c.SwaggerDoc("v1", new OpenApiInfo {
Version = "v1",
Title = "Authentication",
Description = "API allowing for user requests to be authenticated against their credentials",
Contact = new OpenApiContact {
Name = "Me",
Email = "me@fake.com"
}
});
string xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
string xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
});
services.AddCors(options => options.AddDefaultPolicy(
builder => builder.AllowAnyOrigin().
SetIsOriginAllowedToAllowWildcardSubdomains().
AllowAnyMethod().
AllowAnyHeader().
WithHeaders("X-TEST", "true")));
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
}
app.UseSwagger();
app.UseSwaggerUI(c =>
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Authentication API v1"));
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors();
app.UseIdentityServer();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}"));
}
private static X509Certificate2 DownloadCertificate(Secret secret) {
KeyVaultSecret secretValue = new Provider(secret.KeyVaultName).GetSecretAsync(secret.SecretName).Result;
var store = new Pkcs12Store();
using (Stream stream = secret.KeyVaultName.Equals("local")
? new FileStream(Environment.GetEnvironmentVariable(secret.SecretName), FileMode.Open)
: new MemoryStream(Convert.FromBase64String(secretValue.Value))) {
store.Load(stream, Array.Empty<char>());
}
string keyAlias = store.Aliases.Cast<string>().SingleOrDefault(a => store.IsKeyEntry(a));
var key = (RsaPrivateCrtKeyParameters)store.GetKey(keyAlias).Key;
var certificate = new X509Certificate2(
DotNetUtilities.ToX509Certificate(store.GetCertificate(keyAlias).Certificate));
var rsa = new RSACryptoServiceProvider();
rsa.ImportParameters(DotNetUtilities.ToRSAParameters(key));
return RSACertificateExtensions.CopyWithPrivateKey(certificate, rsa);
}
}
在我所有服务的 Startup.cs
中,我有以下内容:
services.AddAuthentication(configuration.SchemeType).
AddJwtBearer("Bearer", options => {
options.Authority = "https://mytest.com/auth"; // Endpoint of the authentication service
options.TokenValidationParameters = new TokenValidationParameters {
ValidateAudience = false
};
});
// Ensure that the claim type is verified as well
services.AddAuthorization(options => options.AddPolicy("ClientIdPolicy", policy =>
policy.RequireClaim("client_id", "b778a2ad-090d-4525-8954-6411de2cd339", "abb9c89c-a018-4b0f-9a0f-4e701c637665")));
我遇到的问题是这一直失败。在尝试调试问题之后,我意识到我并不真正理解这样做的目的。是否验证 JWT 上的字段以确保它们有效?如果是这样,我应该为 Authority
提供什么值?我还需要设置其他字段吗?
更新:
经过进一步调查,我发现请求 return 的 WWW-Authenticate
响应 header 包含 Bearer error="invalid_token", error_description="The signature key was not found"
。看来我错误地配置了我的身份验证服务或我的下游服务,但我不确定是哪一个。
我设置 Identity Server 4 来发布 JWT 令牌来验证用户。在 Identity Server 4 中,我设置了以下内容:
public class Resources {
public static IEnumerable<ApiResource> GetResources() {
return new[] {
new ApiResource {
Name = "Test.API",
DisplayName = "Test API",
Description = "Allow the user access to the test API",
Scopes = new List<string> { "Core API" },
UserClaims = new List<string> { "General", "Admin" }
}
};
}
public static IEnumerable<ApiScope> GetScopes() {
return new[] {
new ApiScope("Core.API", "Allow access to the test API")
};
}
public static IEnumerable<Client> GetClients() {
return new List<Client>() {
new Client {
ClientName = "Test Client",
ClientId = "b778a2ad-090d-4525-8954-6411de2cd339",
ClientSecrets = new List<Secret> { new Secret("random_text".Sha512()) },
AllowedScopes = new List<string> { "Core.API" },
AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials,
},
new Client {
ClientName = "Test Web App",
ClientId = "abb9c89c-a018-4b0f-9a0f-4e701c637665",
ClientSecrets = new List<Secret> { new Secret("other_random_text".Sha512()) },
AllowedGrantTypes = GrantTypes.Hybrid,
RequirePkce = false,
AllowRememberConsent = false,
AllowedScopes = new List<string>
{
StandardScopes.OpenId,
StandardScopes.Profile,
StandardScopes.Address,
StandardScopes.Email,
"Core.API",
"roles"
}
}
};
}
public static IEnumerable<IdentityResource> GetIdentities() {
return new[] {
new IdentityResources.Email(),
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResource {
Name = "User Role",
UserClaims = new List<string> { "Admin", "General" }
}
};
}
}
public class Startup {
public Startup(IConfiguration configuration, IWebHostEnvironment appEnv) {
Configuration = configuration;
CurrentEnvironment = appEnv;
}
public IConfiguration Configuration { get; }
private IWebHostEnvironment CurrentEnvironment { get; set; }
public void ConfigureServices(IServiceCollection services) {
services.AddScoped<IUserRequester, UserRequester>(_ =>
new UserRequester(Configuration.GetSection("AzureTableStore.UserLogin").Get<TableStoreConfiguration>()));
services.AddControllers().AddNewtonsoftJson(options =>
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver());
IIdentityServerBuilder builder = services.AddIdentityServer();
if (CurrentEnvironment.IsDevelopment()) {
builder.AddDeveloperSigningCredential();
} else {
X509Certificate2 certData = DownloadCertificate(Configuration.GetSection("APICertificate").Get<Secret>());
builder.AddSigningCredential(certData);
}
builder.AddInMemoryClients(Resources.GetClients());
builder.AddInMemoryIdentityResources(Resources.GetIdentities());
builder.AddInMemoryApiResources(Resources.GetResources());
builder.AddInMemoryApiScopes(Resources.GetScopes());
builder.Services.Configure<TableStoreConfiguration>(Configuration.GetSection("AzureTableStore.UserLogin"));
builder.Services.Configure<RedisConfiguration>(Configuration.GetSection("RedisCache"));
builder.Services.AddTransient<IRedisConnection, RedisConnection>();
builder.Services.AddTransient<IUserRequester, UserRequester>();
builder.Services.AddTransient<IProfileService, ProfileService>();
builder.Services.AddTransient<IResourceOwnerPasswordValidator, PasswordValidator>();
builder.Services.AddTransient<IAuthorizationCodeStore, AuthorizationCodeStore>();
builder.Services.AddTransient<IReferenceTokenStore, ReferenceTokenStore>();
builder.Services.AddTransient<IRefreshTokenStore, RefreshTokenStore>();
builder.Services.AddTransient<IUserConsentStore, UserConsentStore>();
services.AddSwaggerGen(c => {
c.SwaggerDoc("v1", new OpenApiInfo {
Version = "v1",
Title = "Authentication",
Description = "API allowing for user requests to be authenticated against their credentials",
Contact = new OpenApiContact {
Name = "Me",
Email = "me@fake.com"
}
});
string xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
string xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
});
services.AddCors(options => options.AddDefaultPolicy(
builder => builder.AllowAnyOrigin().
SetIsOriginAllowedToAllowWildcardSubdomains().
AllowAnyMethod().
AllowAnyHeader().
WithHeaders("X-TEST", "true")));
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
}
app.UseSwagger();
app.UseSwaggerUI(c =>
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Authentication API v1"));
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors();
app.UseIdentityServer();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}"));
}
private static X509Certificate2 DownloadCertificate(Secret secret) {
KeyVaultSecret secretValue = new Provider(secret.KeyVaultName).GetSecretAsync(secret.SecretName).Result;
var store = new Pkcs12Store();
using (Stream stream = secret.KeyVaultName.Equals("local")
? new FileStream(Environment.GetEnvironmentVariable(secret.SecretName), FileMode.Open)
: new MemoryStream(Convert.FromBase64String(secretValue.Value))) {
store.Load(stream, Array.Empty<char>());
}
string keyAlias = store.Aliases.Cast<string>().SingleOrDefault(a => store.IsKeyEntry(a));
var key = (RsaPrivateCrtKeyParameters)store.GetKey(keyAlias).Key;
var certificate = new X509Certificate2(
DotNetUtilities.ToX509Certificate(store.GetCertificate(keyAlias).Certificate));
var rsa = new RSACryptoServiceProvider();
rsa.ImportParameters(DotNetUtilities.ToRSAParameters(key));
return RSACertificateExtensions.CopyWithPrivateKey(certificate, rsa);
}
}
在我所有服务的 Startup.cs
中,我有以下内容:
services.AddAuthentication(configuration.SchemeType).
AddJwtBearer("Bearer", options => {
options.Authority = "https://mytest.com/auth"; // Endpoint of the authentication service
options.TokenValidationParameters = new TokenValidationParameters {
ValidateAudience = false
};
});
// Ensure that the claim type is verified as well
services.AddAuthorization(options => options.AddPolicy("ClientIdPolicy", policy =>
policy.RequireClaim("client_id", "b778a2ad-090d-4525-8954-6411de2cd339", "abb9c89c-a018-4b0f-9a0f-4e701c637665")));
我遇到的问题是这一直失败。在尝试调试问题之后,我意识到我并不真正理解这样做的目的。是否验证 JWT 上的字段以确保它们有效?如果是这样,我应该为 Authority
提供什么值?我还需要设置其他字段吗?
更新:
经过进一步调查,我发现请求 return 的 WWW-Authenticate
响应 header 包含 Bearer error="invalid_token", error_description="The signature key was not found"
。看来我错误地配置了我的身份验证服务或我的下游服务,但我不确定是哪一个。