Wykonaj wywołanie HTTPS przy użyciu HttpClient

157

Używam HttpClientdo wykonywania wywołań WebApi przy użyciu języka C #. Wydaje się schludny i szybki w porównaniu do WebClient. Jednak utknąłem podczas wykonywania Httpspołączeń.

Jak mogę zrobić poniższy kod, aby wykonywać Httpspołączenia?

HttpClient httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("https://foobar.com/");
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(
                new MediaTypeWithQualityHeaderValue("application/xml"));

var task = httpClient.PostAsXmlAsync<DeviceRequest>(
                "api/SaveData", request);

EDYCJA 1: Powyższy kod działa dobrze do wykonywania połączeń http. Ale kiedy zmieniam schemat na https to nie działa. Oto uzyskany błąd:

Połączenie podstawowe zostało zamknięte: nie można ustanowić relacji zaufania dla bezpiecznego kanału SSL / TLS.

EDYCJA 2: Zmiana schematu na https to: krok pierwszy.

Jak dostarczyć certyfikat i klucz publiczny / prywatny wraz z żądaniem C #.

Abhijeet
źródło
2
wykonujesz połączenia https po prostu podającnew Uri("https://foobar.com/");
felickz
2
Jestem zmieszany. Czy to już nie działa? Czy pojawia się błąd? (Edycja: opublikowane przed zmianą identyfikatora URI przez OP z https na http)
Tim

Odpowiedzi:

215

Jeśli serwer obsługuje tylko wyższą wersję TLS, taką jak tylko TLS 1.2, nadal będzie się nie powieść, chyba że komputer kliencki jest domyślnie skonfigurowany do korzystania z wyższej wersji TLS. Aby rozwiązać ten problem, dodaj do kodu następujący kod.

System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;

Modyfikowanie przykładowego kodu byłoby

HttpClient httpClient = new HttpClient();   

//specify to use TLS 1.2 as default connection
System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;

httpClient.BaseAddress = new Uri("https://foobar.com/");
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));

var task = httpClient.PostAsXmlAsync<DeviceRequest>("api/SaveData", request);
Ronald Ramos
źródło
@DerekS Cieszę się i nie ma za co. Jeśli z jakiegoś powodu nie możesz zmodyfikować kodu w ustawieniach produkcyjnych, ale możesz zrobić trochę administratora na serwerze, używam tego narzędzia do domyślnej konfiguracji TLS 1.2. nartac.com/Products/IISCrypto
Ronald Ramos
SecurityProtocolType.Tls12nie mogłem znaleźć tych wartości wyliczenia, o których wspominałeś
JobaDiniz,
1
@JobaDiniz korzysta z platformy .NET 4.5 lub nowszej i zawiera przestrzeń nazw System.Net.
Ronald Ramos
Czy ustawienie SecurityProtocol musi nastąpić tylko raz? Podobnie jak podczas uruchamiania aplikacji?
CamHart
To działa świetnie. Dzięki. Odkryłem również, że nie muszę czyścić żadnych nagłówków, używając tego klienta HttpClient = nowy HttpClient (); ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; HttpResponseMessage response = czekaj na klienta.GetAsync ("https: // ...");
AtLeastTheresToast
127

Po prostu określ HTTPS w identyfikatorze URI.

new Uri("https://foobar.com/");

Foobar.com będzie musiał mieć zaufany certyfikat SSL, w przeciwnym razie Twoje połączenia zakończą się niepowodzeniem z niezaufanym błędem.

EDYTUJ odpowiedź: ClientCertificates z HttpClient

WebRequestHandler handler = new WebRequestHandler();
X509Certificate2 certificate = GetMyX509Certificate();
handler.ClientCertificates.Add(certificate);
HttpClient client = new HttpClient(handler);

EDYTUJ Odpowiedź 2: Jeśli serwer, z którym się łączysz, wyłączył SSL, TLS 1.0 i 1.1 i nadal korzystasz z .NET Framework 4.5 (lub starszego), musisz dokonać wyboru

  1. Uaktualnij do .Net 4.6+ (domyślnie obsługuje TLS 1.2 )
  2. Dodaj zmiany w rejestrze, aby polecić 4.5, aby połączyć się przez TLS1.2 (Zobacz: zapis Salesforce dla kompatybilności i klucze do zmiany LUB sprawdź IISCrypto zobacz komentarze Ronalda Ramosa )
  3. Dodaj kod aplikacji, aby ręcznie skonfigurować .NET do łączenia się przez TLS1.2 (zobacz odpowiedź Ronalda Ramosa )
felickz
źródło
@felickz świetna odpowiedź. czy istnieje odpowiednik biblioteki WebRequestHandler dla Windows Phone 8?
Billatron,
@Billatron różne biblioteki dla WP / Win8 .. zobacz
felickz
6
Przy opracowywaniu lub radzenia sobie z własnym podpisał CERT można zignorować niezaufane błędy cert z następującymi zasadami: ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
deweloper
6
Co to jest GetMyX509Certificate?
Fandi Susanto,
6
@developer miejmy nadzieję, że ten kod nie wślizgnie się do twojej wersji produkcyjnej :)
felickz
15

Twój kod powinien zostać zmodyfikowany w ten sposób:

httpClient.BaseAddress = new Uri("https://foobar.com/");

Musisz po prostu użyć https:schematu URI. Jest to przydatna strona tutaj na MSDN o bezpiecznych połączeń HTTP. W rzeczy samej:

Użyj schematu https: URI

Protokół HTTP definiuje dwa schematy URI:

http: używany do połączeń nieszyfrowanych.

https: używany do bezpiecznych połączeń, które powinny być szyfrowane. Ta opcja wykorzystuje również certyfikaty cyfrowe i urzędy certyfikacji do sprawdzenia, czy serwer jest tym, za kogo się podaje.

Ponadto weź pod uwagę, że połączenia HTTPS używają certyfikatu SSL. Upewnij się, że Twoje bezpieczne połączenie ma ten certyfikat, w przeciwnym razie żądania zakończą się niepowodzeniem.

EDYTOWAĆ:

Powyższy kod działa dobrze przy wykonywaniu połączeń HTTP. Ale kiedy zmienię schemat na https to nie działa, napiszę błąd.

Co to znaczy, że nie działa? Żądania zawodzą? Został zgłoszony wyjątek? Wyjaśnij swoje pytanie.

Jeśli żądania nie powiodą się, problemem powinien być certyfikat SSL.

Aby rozwiązać problem, możesz użyć klasy, HttpWebRequesta następnie jej właściwości ClientCertificate. Ponadto możesz znaleźć tutaj przydatny przykład dotyczący tworzenia żądania HTTPS przy użyciu certyfikatu.

Przykład jest następujący (jak pokazano na stronie MSDN połączonej wcześniej):

//You must change the path to point to your .cer file location. 
X509Certificate Cert = X509Certificate.CreateFromCertFile("C:\\mycert.cer");
// Handle any certificate errors on the certificate from the server.
ServicePointManager.CertificatePolicy = new CertPolicy();
// You must change the URL to point to your Web server.
HttpWebRequest Request = (HttpWebRequest)WebRequest.Create("https://YourServer/sample.asp");
Request.ClientCertificates.Add(Cert);
Request.UserAgent = "Client Cert Sample";
Request.Method = "GET";
HttpWebResponse Response = (HttpWebResponse)Request.GetResponse();
Alberto Solano
źródło
Precyzyjnie sztuczka polega na wysyłaniu certyfikatów wraz z żądaniem. jak to zrobić?
Abhijeet
Skąd wziąć „C: \\ mycert.cer”? Jak wygenerować plik mycert.cer?
masiboo
@masiboo To, co napisałem, odpowiada, jak wykonywać połączenia HTTPS. Zapytaj Google, jak wygenerować plik * .cer, a sam znajdziesz odpowiedź.
Alberto Solano
9

Podczas łączenia z httpsotrzymałem również ten błąd, dodaję tę linię wcześniej HttpClient httpClient = new HttpClient();i łączę się pomyślnie:

ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };

Wiem to z tej odpowiedzi i innej podobnej odpowiedzi, a komentarz wspomina:

Jest to hack przydatny w programowaniu, więc umieszczenie wokół niego instrukcji #if DEBUG #endif jest najmniejszą rzeczą, którą powinieneś zrobić, aby uczynić to bezpieczniejszym i zatrzymać to, co kończy się w produkcji

Poza tym nie wypróbowałem metody w Another Answer, która używa new X509Certificate()lub new X509Certificate2()tworzy certyfikat, nie jestem pewien, czy po prostu tworzenie przez new()będzie działać, czy nie.

EDYCJA: Niektóre odniesienia:

Utwórz certyfikat serwera z podpisem własnym w usługach IIS 7

Importowanie i eksportowanie certyfikatów SSL w usługach IIS 7

Konwertuj .pfx na .cer

Najlepsze rozwiązania dotyczące korzystania z ServerCertificateValidationCallback

Uważam, że wartość odcisku palca jest równa x509certificate.GetCertHashString():

Pobierz odcisk palca certyfikatu

yu yang Jian
źródło
6

Miałem ten sam problem podczas łączenia się z GitHubem, co wymaga agenta użytkownika. Dlatego wystarczy podać to, a nie wygenerować certyfikatu

var client = new HttpClient();

client.BaseAddress = new Uri("https://api.github.com");
client.DefaultRequestHeaders.Add(
    "Authorization",
    "token 123456789307d8c1d138ddb0848ede028ed30567");
client.DefaultRequestHeaders.Accept.Add(
    new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Add(
    "User-Agent",
    "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36");
Carlo V. Dango
źródło
2
Powinieneś unieważnić ten token, który udostępniłeś w Internecie, myślę, że GitHub mógł to zrobić automatycznie.
Amias
21
czy naprawdę myślisz, że mój token zaczyna się od 123456789??
Carlo V. Dango
5

Istnieje nieglobalne ustawienie na poziomie HttpClientHandler:

var handler = new HttpClientHandler()
{
    SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls
};

var client = new HttpClient(handler);

W ten sposób można włączyć najnowsze wersje TLS.

Zauważ, że wartość domyślna SslProtocols.Defaultto faktycznie SslProtocols.Ssl3 | SslProtocols.Tls(sprawdzana dla .Net Core 2.1 i .Net Framework 4.7.1).

stop-cran
źródło
Dziękuję za odpowiedź. Mała uwaga: zgodnie z tą dokumentacją: docs.microsoft.com/en-us/dotnet/api/ ... Wymaga przynajmniej frameworka 4.7.1.
GELR
@GELR tak, masz rację. Powyższy kod powoduje błąd wykonania w .Net 4.6.1. Naprawiono odpowiedź.
stop-cran
W poprzednich wersjach .NET Framework ta właściwość jest prywatna
JotaBe
Na Net Core 3.1 to wszystko, co dla mnie zadziałało. Ustawienie globalnego System.Net.ServicePointManager.SecurityProtocol = xxx - miało absolutnie zerowy wpływ na śledzenie pakietów.
Rowan Smith
3

Samo określenie HTTPS w identyfikatorze URI powinno załatwić sprawę.

httpClient.BaseAddress = new Uri("https://foobar.com/");

Jeśli żądanie działa z HTTP, ale kończy się niepowodzeniem z HTTPS, z pewnością jest to problem z certyfikatem . Upewnij się, że dzwoniący ufa wystawcy certyfikatu i że certyfikat nie wygasł. Szybkim i łatwym sposobem sprawdzenia tego jest wykonanie zapytania w przeglądarce.

Możesz również chcieć sprawdzić na serwerze (czy jest twój i / lub jeśli możesz), czy jest skonfigurowany do obsługi żądań HTTPS prawidłowo.

Crono
źródło
: Mam podobny problem: wykonałem zapytanie w przeglądarce i certyfikat jest ważny (połączenie przechodzi, jest autoryzowany jako poziom serwisowy i wszystko działa jak urok), ale programowo nie działa. Co jeszcze może być? jeśli używam dokładnie tego samego certyfikatu z przeglądarki „ręcznie wywołując usługę” niż z aplikacji sieci Web wywołującej programowo usługę?
Carlos,
2

Otrzymałem również błąd:

Połączenie podstawowe zostało zamknięte: nie można ustanowić relacji zaufania dla bezpiecznego kanału SSL / TLS.

... z aplikacją docelową Xamarin Forms dla systemu Android próbującą zażądać zasobów od dostawcy interfejsu API, który wymagał protokołu TLS 1.3.

Rozwiązanie polegało na zaktualizowaniu konfiguracji projektu w celu wymiany klienta HTTP „zarządzanego” (.NET) platformy Xamarin (który nie obsługuje protokołu TLS 1.3 od wersji Xamarin Forms w wersji 2.5) i zamiast tego użyć natywnego klienta systemu Android.

To proste przełączanie projektu w Visual Studio. Zobacz zrzut ekranu poniżej.

  • Właściwości projektu
  • Opcje Androida
  • zaawansowane
  • Element listy
  • Zmień „Implementację HttpClient” na „Android”
  • Zmień implementację SSL / TLS na „Native TLS 1.2+”

wprowadź opis obrazu tutaj

MSC
źródło
1

Dodaj poniższe deklaracje do swojej klasy:

public const SslProtocols _Tls12 = (SslProtocols)0x00000C00;
public const SecurityProtocolType Tls12 = (SecurityProtocolType)_Tls12;

Po:

var client = new HttpClient();

I:

ServicePointManager.SecurityProtocol = Tls12;
System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 /*| SecurityProtocolType.Tls */| Tls12;

Szczęśliwy? :)

Greg Stawski
źródło
1

Miałem ten problem iw moim przypadku rozwiązanie było głupio proste: otwórz Visual Studio z uprawnieniami administratora. Wypróbowałem wszystkie powyższe rozwiązania i nie działało, dopóki tego nie zrobiłem. Mam nadzieję, że zaoszczędzi to komuś cennego czasu.

Lucian Satmarean
źródło
Ponieważ jesteś nowy na stronie, możesz nie wiedzieć, że jeśli obok odpowiedzi znajduje się zielony znacznik, oznacza to, że została zaakceptowana przez OP jako właściwe rozwiązanie jego / jej problemu. O ile nie możesz udzielić lepszej, pełniejszej odpowiedzi, prawdopodobnie najlepiej nie podawać innej odpowiedzi, zwłaszcza w przypadku tak starego pytania.
Sam W
4
Cóż, ja też miałem problem, a zielona odpowiedź w ogóle nie pomogła. Myślałem, że podam alternatywne rozwiązanie, które mi pomogło, ale hej, następnym razem tego nie zrobię. Dzięki za minusy;) życzę miłego dnia.
Lucian Satmarean
0

Możesz spróbować użyć pakietu ModernHttpClient Nuget: Po pobraniu pakietu możesz go zaimplementować w następujący sposób:

 var handler = new ModernHttpClient.NativeMessageHandler()
 {
     UseProxy = true,
 };


 handler.ClientCertificateOptions = ClientCertificateOption.Automatic;
 handler.PreAuthenticate = true;
 HttpClient client = new HttpClient(handler);
Uchenna Nnodim
źródło
Niestety u mnie to nie zadziałało. Rozwiązaniem było włączenie protokołu TLS 1.3, przełączając się z klienta HTTP zarządzanego przez platformę Xamarin na natywnego klienta http dla systemu Android. Zobacz moją odpowiedź poniżej.
MSC
0

Zgadzam się z felickz, ale chcę też dodać przykład do wyjaśnienia użycia w języku c #. Używam SSL w usłudze Windows w następujący sposób.

    var certificatePath = System.IO.Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "bin");
    gateway = new GatewayService();
    gateway.PreAuthenticate = true;


    X509Certificate2 cert = new X509Certificate2(certificatePath + @"\Attached\my_certificate.pfx","certificate_password");
    gateway.ClientCertificates.Add(cert);

    ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
    gateway.UserAgent = Guid.NewGuid().ToString();
    gateway.Timeout = int.MaxValue;

Jeśli mam zamiar używać go w aplikacji internetowej, po prostu zmieniam implementację po stronie proxy w następujący sposób:

public partial class GatewayService : System.Web.Services.Protocols.SoapHttpClientProtocol // to => Microsoft.Web.Services2.WebServicesClientProtocol
Hamit YILDIRIM
źródło
-4

W przypadku błędu:

Połączenie podstawowe zostało zamknięte: nie można ustanowić relacji zaufania dla bezpiecznego kanału SSL / TLS.

Myślę, że musisz bezwarunkowo zaakceptować certyfikat z następującym kodem

ServicePointManager.ServerCertificateValidationCallback += 
    (sender, cert, chain, sslPolicyErrors) => true;

jak napisał Oppositional w odpowiedzi na pytanie klienta .NET łączącego się z SSL Web API .

Nemanja Simović
źródło
4
To jest pomijanie weryfikacji certyfikatu. Nigdy nie rób tego w produkcji.
John Korsnes
Cóż, @JohnKorsnes, jeśli Abhijeet ma dostęp do jakiegoś publicznego serwera WebAPI, nie sądzę, aby mógł wiele z tym zrobić. To był mój przypadek.
Nemanja Simović