Я несколько дней безуспешно зависаю над этой проблемой, и ни один ответ на разные посты на разных сайтах не помог мне решить эту проблему.
Я работаю в системе Windows 10 и внедряю ее в VisualStudio 2017.
С AspNetCore я реализовал следующие проекты:
1.) Web.AuthServer: IdentityServer4 для аутентификации.
2.) Web.ApiServer: первый SignalR-сервер.
3.) Web.ApiSwitch: второй SignalR-сервер.
Он имеет HostedService с двумя SignalR-клиентами в качестве
«мост» между двумя SignalR-серверами.>
Web.ApiSwitch запускает свой HostedService, который подключается к себе и к Web.ApiServer, включая аутентификацию на Web.AuthServer. Это работало хорошо, пока они работали с некоторым URL "localhost: PORT".
Теперь я попытался запустить все проекты с помощью «MyIP: PORT». Web.AuthServer использует HTTPS вместе с самозаверяющим сертификатом (созданным с помощью OpenSSL).
Сам сертификат имеет сборку beend со следующими командными строками:
Генерация закрытого ключа:
openssl req -x509 -newkey rsa:4096 -sha256 -nodes -keyout IdentityServer4Auth.key -out IdentityServer4Auth.crt -subj "/CN=example.com" -days 3650
Генерация сертификата:
openssl pkcs12 -export -out IdentityServer4Auth.pfx -inkey IdentityServer4Auth.key -in IdentityServer4Auth.crt -certfile IdentityServer4Auth.crt
Файл был добавлен в mmc:
1.) Файл -> Добавить или удалить оснастку -> Сертификаты -> Добавить -> Учетная запись компьютера -> ОК
2.) Импортируйте сертификат (.cer) в личный список -> Доверенные корневые центры сертификации)
3.) Импортируйте файл pfx с поддержкой экспортируемого закрытого ключа в персональные -> сертификаты.
Код Web.AuthServer:
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseKestrel(options =>
{
options.Listen(IPAddress.Any, 5000, listenOptions =>
{
listenOptions.UseHttps();
});
})
.UseStartup<Startup>()
.ConfigureLogging(builder =>
{
builder.ClearProviders();
builder.AddSerilog();
})
.Build();
Web.AuthSever - ConfigureServices:
public void ConfigureServices(IServiceCollection services)
{
// Gets connection strings from "appsettings.json".
string csApplicationContext = Configuration.GetConnectionString("ApplicationContext");
string csConfigurationStore = Configuration.GetConnectionString("ConfigurationStore");
var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
var settings = JsonFileManager<ServerSettings>.Load(AppDomain.CurrentDomain.BaseDirectory + "Config\\svConf.json");
// Add cross origin resource sharing.
services.AddCors(options =>
{
options.AddPolicy("default", policy =>
{
policy.WithOrigins(settings.CorsOrigins)
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
});
// Add bearer token authentication.
services.AddAuthentication()
.AddJwtBearer(jwt =>
{
jwt.Authority = settings.JWTBearerSettings.Authority;
jwt.Audience = settings.JWTBearerSettings.Audience;
jwt.RequireHttpsMetadata = settings.JWTBearerSettings.RequireHttpsMetadata;
jwt.Validate();
});
services.AddPolicyServerClient(Configuration.GetSection("Policy"))
.AddAuthorizationPermissionPolicies();
// DB und User registieren für DI
services.AddDbContext<ApplicationDbContext>(builder =>
builder.UseSqlite(csApplicationContext, sqlOptions =>
sqlOptions.MigrationsAssembly(migrationsAssembly)));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddTransient<IClientStore, ClientService>();
// Add IS4 as authentication server.
var is4Builder = services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
options.Events.RaiseInformationEvents = true;
})
// Add config data (clients, resources, CORS).
.AddConfigurationStore(options =>
options.ConfigureDbContext = builder =>
builder.UseSqlite(csConfigurationStore, sqlOptions =>
sqlOptions.MigrationsAssembly(migrationsAssembly)))
.AddClientStore<ClientService>()
.AddAspNetIdentity<ApplicationUser>();
SigninCredentialExtension.AddSigninCredentialFromConfig(is4Builder, Configuration.GetSection("SigninKeyCredentials"), Logger);
services.AddMvc(options =>
{
// this sets up a default authorization policy for the application
// in this case, authenticated users are required (besides controllers/actions that have [AllowAnonymous]
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
options.SslPort = 5000;
options.Filters.Add(new RequireHttpsAttribute());
});
}
Web.AuthSever - Настройка:
public async void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
app.UseDeveloperExceptionPage();
else
app.UseExceptionHandler("/Home/Error");
// Use specific cross origin resource sharing configuration.
app.UseCors("default");
app.UseDefaultFiles();
app.UsePolicyServerClaims();
app.UseStaticFiles();
app.UseHttpsRedirection();
app.UseIdentityServer();
// Adding test data to database.
await InitializeDbTestData.GenerateTestData(app);
app.UseMvcWithDefaultRoute();
}
Web.AuthSever - SigninCredentialExtension:
public static class SigninCredentialExtension
{
private const string KeyType = "KeyType";
private const string KeyTypeKeyFile = "KeyFile";
private const string KeyTypeKeyStore = "KeyStore";
private const string KeyTypeTemporary = "Temporary";
private const string KeyFilePath = "KeyFilePath";
private const string KeyFilePassword = "KeyFilePassword";
private const string KeyStoreIssuer = "KeyStoreIssuer";
public static IIdentityServerBuilder AddSigninCredentialFromConfig(
this IIdentityServerBuilder builder, IConfigurationSection options, ILogger logger)
{
string keyType = options.GetValue<string>(KeyType);
logger.LogDebug($"SigninCredentialExtension keyType is {keyType}");
switch (keyType)
{
case KeyTypeTemporary:
logger.LogDebug($"SigninCredentialExtension adding Developer Signing Credential");
builder.AddDeveloperSigningCredential();
break;
case KeyTypeKeyFile:
AddCertificateFromFile(builder, options, logger);
break;
case KeyTypeKeyStore:
AddCertificateFromStore(builder, options, logger);
break;
}
return builder;
}
public static X509Certificate2 GetCertificateByThumbprint(string thumbprint)
{
using (X509Store certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser))
{
certStore.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certCollection = certStore.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, false);
if (certCollection.Count > 0) return certCollection[0];
}
return null;
}
private static void AddCertificateFromStore(IIdentityServerBuilder builder,
IConfigurationSection options, ILogger logger)
{
var keyIssuer = options.GetValue<string>(KeyStoreIssuer);
logger.LogDebug($"SigninCredentialExtension adding key from store by {keyIssuer}");
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
var certificates = store.Certificates.Find(X509FindType.FindByIssuerName, keyIssuer, true);
if (certificates.Count > 0)
{
builder.AddSigningCredential(certificates[0]);
builder.AddValidationKey(certificates[0]);
}
else
logger.LogError("A matching key couldn't be found in the store");
}
private static void AddCertificateFromFile(IIdentityServerBuilder builder,
IConfigurationSection options, ILogger logger)
{
var keyFilePath = options.GetValue<string>(KeyFilePath);
var keyFilePassword = options.GetValue<string>(KeyFilePassword);
if (File.Exists(keyFilePath))
{
logger.LogDebug($"SigninCredentialExtension adding key from file {keyFilePath}");
builder.AddSigningCredential(new X509Certificate2(keyFilePath, keyFilePassword));
}
else
{
logger.LogError($"SigninCredentialExtension cannot find key file {keyFilePath}");
}
}
}
Код Web.ApiServer:
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseKestrel(options =>
{
options.Listen(IPAddress.Any, 5004, listenOptions =>
{
listenOptions.UseHttps();
});
})
.UseStartup<Startup>()
.ConfigureLogging(builder =>
{
builder.ClearProviders();
builder.AddSerilog();
})
.Build();
Web.ApiServer - ConfigureServices:
public void ConfigureServices(IServiceCollection services)
{
// Add cross origin resource sharing.
services.AddCors(options =>
{
options.AddPolicy("default", policy =>
{
policy.WithOrigins(_settings.CorsOrigins)
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
});
// Add bearer token authentication and our IS4 as authentication server.
services.AddAuthentication(_settings.ISAuthenticationSettings.DefaultScheme)
.AddIdentityServerAuthentication(options =>
{
options.Authority = _settings.ISAuthenticationSettings.Authority;
options.RequireHttpsMetadata = _settings.ISAuthenticationSettings.RequireHttpsMetadata;
options.ApiName = _settings.ISAuthenticationSettings.ApiName;
// Handling the token from query string in due to the reason
// that signalR clients are handling them over it.
options.TokenRetriever = new Func<HttpRequest, string>(req =>
{
var fromHeader = TokenRetrieval.FromAuthorizationHeader();
var fromQuery = TokenRetrieval.FromQueryString();
return fromHeader(req) ?? fromQuery(req);
});
options.Validate();
});
// Add singalR as event bus.
services.AddSignalR(options => options.EnableDetailedErrors = true);
services.AddMvcCore(options =>
{
options.SslPort = 5003;
options.Filters.Add(new RequireHttpsAttribute());
})
.AddAuthorization()
.AddJsonFormatters();
// Register ConnectionHost as hosted service with its wrapper class.
services.AddSingleton<Microsoft.Extensions.Hosting.IHostedService, ConnectionHost>();
}
Web.ApiServer - Настройка:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
app.UseDeveloperExceptionPage();
app.UseHttpsRedirection();
// Has to be called before UseSignalR and UseMvc!
app.UseAuthentication();
// Use specific cross origin resource sharing configuration.
app.UseCors("default");
app.UseSignalR(routes => routes.MapHub<EventHub>("/live"));
app.UseMvc();
}
Запрос токена или клиенты SignalR:
public static async Task<TokenResponse> RequestTokenAsync(string authority, string clientID, string scope)
{
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync(authority);
if (disco.IsError) throw new Exception(disco.Error);
var response = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = clientID,
ClientSecret = "SomeTestSecret",
Scope = scope
});
if (response.IsError)
{
throw new Exception(response.Error);
}
return response;
}
TokenRetriever ConfigureServices из Web.ApiServer предназначен только для проверки подлинности запущенных клиентов SignalR в связи с тем, что они передают токены через строку запроса. Это делает работу.
Теперь проблема:
Клиенты HostedService в Web.ApiServer пытаются получить токен аутентификации (jwt bearer) из Web.AuthServer, но каждый раз, когда я получаю
следующее исключение:
System.Security.Authentication.AuthenticationException: 'The remote certificate is invalid according to the validation procedure.'
Если я открою браузер и наберу адрес Web.AuthServer «MyIP: 5000», то все будет нормально, после того как я приму самоподписанный сертификат.
Но клиенты HostedService of Web.ApiServer не могут этого сделать.
Как мне избавиться от этого исключения и получить действительный сертификат? Я что-то упускаю при реализации клиента? Надеюсь, кто-нибудь может мне помочь - застрять в этом более 4 дней.