HttpClient无法使用开发证书请求API的解决方法咨询
使用HttpClient通过证书请求API返回403 Forbidden的解决方法
问题场景
我通过以下命令生成了开发环境HTTPS证书:
dotnet dev-certs https -ep dev_cert.pfx -p 1234
随后配置了API的证书认证逻辑,代码如下:
public static class AuthenticationExtension { public static void ConfigureAuthetication(this IServiceCollection services) { services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme) .AddCertificate(options => { var cert = new X509Certificate2(@"D:\dev_cert.pfx", "1234"); options.AllowedCertificateTypes = CertificateTypes.All; options.ChainTrustValidationMode = X509ChainTrustMode.CustomRootTrust; options.CustomTrustStore = new X509Certificate2Collection { cert }; options.RevocationMode = X509RevocationMode.NoCheck; options.ValidateCertificateUse = false; options.Events = new CertificateAuthenticationEvents { OnCertificateValidated = context => { var validationService = context.HttpContext.RequestServices.GetService<ClientCertificateValidationService>(); if (validationService != null && validationService.ValidateCertificate(context.ClientCertificate)) { Console.WriteLine("Success"); context.Success(); } else { Console.WriteLine("invalid cert"); context.Fail("invalid cert"); } return Task.CompletedTask; }, OnChallenge = context => { return Task.CompletedTask; }, OnAuthenticationFailed = context => { Console.WriteLine("Failed"); return Task.CompletedTask; } }; }); services.AddAuthorization(); } }
控制器方法添加了[Authorize]特性:
[HttpGet(Name = "GetWeatherForecast")] [Authorize] public IEnumerable<WeatherForecast> Get() { return Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index), TemperatureC = Random.Shared.Next(-20, 55), Summary = Summaries[Random.Shared.Next(Summaries.Length)] }) .ToArray(); }
测试时,Postman请求正常,但用HttpClient发起请求始终返回Forbidden状态码,而过时的WebRequest却能正常工作。测试代码如下:
using System.Net; using System.Security.Cryptography.X509Certificates; var handler = new HttpClientHandler(); X509Certificate2Collection certificates = new X509Certificate2Collection(); certificates.Import(@"D:\dev_cert.pfx", "1234", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet); handler.ClientCertificates.AddRange(certificates); using var client = new HttpClient(handler); client.BaseAddress = new Uri("https://localhost:7148/"); try { var response = await client.GetAsync("weatherforecast/"); if (response.IsSuccessStatusCode) { var responseContent = await response.Content.ReadAsStringAsync(); Console.WriteLine(responseContent); } else { Console.WriteLine($"Failed {response.StatusCode}"); } } catch (Exception ex) { Console.WriteLine($"Failed {ex}"); } string host = @"https://localhost:7148/weatherforecast"; try { ServicePointManager.ServerCertificateValidationCallback = (a, b, c, d) => true; HttpWebRequest req = (HttpWebRequest)WebRequest.Create(host); req.AllowAutoRedirect = true; req.ClientCertificates = certificates; req.Method = "GET"; WebResponse resp = req.GetResponse(); Stream stream = resp.GetResponseStream(); using (StreamReader reader = new StreamReader(stream)) { string line = reader.ReadLine(); while (line != null) { Console.WriteLine(line); line = reader.ReadLine(); } } stream.Close(); } catch (Exception e) { Console.WriteLine(e); } Console.ReadLine();
解决方法
问题出在HttpClientHandler的两个关键配置缺失:
- 跳过服务器证书验证:和
WebRequest中ServicePointManager.ServerCertificateValidationCallback的作用对应,需要为HttpClientHandler设置ServerCertificateCustomValidationCallback,跳过本地开发证书的信任验证。 - 手动指定客户端证书发送策略:
HttpClientHandler默认不会主动发送手动添加的客户端证书,需要设置ClientCertificateOption.Manual明确告知处理器使用手动添加的证书。
修改后的HttpClient代码如下:
var handler = new HttpClientHandler(); // 明确使用手动添加的客户端证书 handler.ClientCertificateOptions = ClientCertificateOption.Manual; // 跳过服务器证书验证(仅开发环境使用) handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true; X509Certificate2Collection certificates = new X509Certificate2Collection(); certificates.Import(@"D:\dev_cert.pfx", "1234", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet); handler.ClientCertificates.AddRange(certificates); using var client = new HttpClient(handler); client.BaseAddress = new Uri("https://localhost:7148/"); try { // 注意路径去掉末尾斜杠,避免重定向问题 var response = await client.GetAsync("weatherforecast"); if (response.IsSuccessStatusCode) { var responseContent = await response.Content.ReadAsStringAsync(); Console.WriteLine(responseContent); } else { Console.WriteLine($"Failed {response.StatusCode}"); } } catch (Exception ex) { Console.WriteLine($"Failed {ex}"); }
额外说明
ClientCertificateOption.Manual确保处理器会发送我们手动添加的客户端证书,避免证书被忽略。ServerCertificateCustomValidationCallback仅建议在开发环境使用,生产环境需使用受信任的合法证书。- 如果证书私钥访问出现问题,可以尝试调整
X509KeyStorageFlags参数,比如使用X509KeyStorageFlags.Exportable | X509KeyStorageFlags.UserKeySet。
内容的提问来源于stack exchange,提问作者Renato Ramos Nascimento
相关产品推荐
相关产品推荐

