Как решить ошибку CERTIFICATE_VERIFY_FAILED при выполнении запроса POST?
Я отправляю почтовый запрос в Дарт. Он дает ответ, когда я тестирую его на инструменте тестирования API, таком как Postman. Но когда я запускаю приложение. Это дает мне следующую ошибку:
E/flutter ( 6264): HandshakeException: Handshake error in client (OS Error: E/flutter ( 6264): CERTIFICATE_VERIFY_FAILED: unable to get local issuer certificate(handshake.cc:363))
Вот мой код функции -
Будущее getAccessToken(String url) async {
try {
http.post('url',
body: {
"email": "xyz@xyz.com",
"password": "1234"
}).then((response) {
print("Reponse status : ${response.statusCode}");
print("Response body : ${response.body}");
var myresponse = jsonDecode(response.body);
String token = myresponse["token"];
});
} catch (e) {
print(e.toString());
}
Вот полное тело ошибки -
E/flutter ( 6264): [ERROR:flutter/shell/common/shell.cc(184)] Dart Error: Unhandled exception: E/flutter ( 6264): HandshakeException: Handshake error in client (OS Error: E/flutter ( 6264): CERTIFICATE_VERIFY_FAILED: unable to get local issuer certificate(handshake.cc:363)) E/flutter ( 6264): #0 IOClient.send (package:http/src/io_client.dart:33:23) E/flutter ( 6264): <asynchronous suspension> E/flutter ( 6264): #1 BaseClient._sendUnstreamed (package:http/src/base_client.dart:169:38) E/flutter ( 6264): <asynchronous suspension> E/flutter ( 6264): #2 BaseClient.post (package:http/src/base_client.dart:54:7) E/flutter ( 6264): #3 post.<anonymous closure> (package:http/http.dart:70:16) E/flutter ( 6264): #4 _withClient (package:http/http.dart:166:20) E/flutter ( 6264): <asynchronous suspension> E/flutter ( 6264): #5 post (package:http/http.dart:69:5) E/flutter ( 6264): #6
_MyLoginFormState.getAccessToken (package:chart/main.dart:74:7) E/flutter ( 6264): <asynchronous suspension> E/flutter ( 6264): #7
_MyLoginFormState.build.<anonymous closure> (package:chart/main.dart:64:29)
33 ответа
Правильный (но плохой) способ сделать это, как я выяснил, - разрешить все сертификаты.
HttpClient client = new HttpClient();
client.badCertificateCallback =((X509Certificate cert, String host, int port) => true);
String url ='xyz@xyz.com';
Map map = { "email" : "email" , "password" : "password};
HttpClientRequest request = await client.postUrl(Uri.parse(url));
request.headers.set('content-type', 'application/json');
request.add(utf8.encode(json.encode(map)));
HttpClientResponse response = await request.close();
String reply = await response.transform(utf8.decoder).join();
print(reply);
Просто для ясности, специально для того, чтобы новички могли трепыхаться / дротикнуть, вот что вам нужно сделать, чтобы включить эту опцию глобально в вашем проекте:
- В вашем файле main.dart добавьте или импортируйте следующий класс:
class MyHttpOverrides extends HttpOverrides{ @override HttpClient createHttpClient(SecurityContext context){ return super.createHttpClient(context) ..badCertificateCallback = (X509Certificate cert, String host, int port)=> true; } }
- В вашей основной функции добавьте следующую строку после определения функции:
HttpOverrides.global = новый MyHttpOverrides();
Этот комментарий был очень полезен для обсуждения этого вопроса
Загрузите сертификат с https://letsencrypt.org/certs/lets-encrypt-r3.pem
Добавьте этот файл в корневой каталог проекта assets / ca / Flutter.
Добавьте каталог assets / ca / assets в pubspec.yaml
Добавьте этот код при инициализации вашего приложения:
void main() async { WidgetsFlutterBinding.ensureInitialized(); ByteData data = await PlatformAssetBundle().load('assets/ca/lets-encrypt-r3.pem'); SecurityContext.defaultContext.setTrustedCertificatesBytes(data.buffer.asUint8List()); runApp(MyApp()); }
Он работает с цепочкой по умолчанию, поэтому никаких изменений на сервере не требуется. Клиенты Android <7.1.1 по-прежнему будут иметь доступ в контексте браузера.
Если вы используете библиотеку Dio, просто сделайте следующее:
Dio dio = new Dio();
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
(HttpClient client) {
client.badCertificateCallback =
(X509Certificate cert, String host, int port) => true;
return client;
};
Этот код работает для меня
class MyHttpOverrides extends HttpOverrides{
@override
HttpClient createHttpClient(SecurityContext context){
return super.createHttpClient(context)
..badCertificateCallback = (X509Certificate cert, String host, int port)=> true;
}
}
void main(){
HttpOverrides.global = new MyHttpOverrides();
runApp(MyApp());
}
Я думаю, что то же самое и с вами ...
Наилучший подход (я думаю) - разрешить сертификат для доверенных хостов, поэтому, если ваш хост API "api.my_app"
вы можете разрешить сертификаты только с этого хоста:
HttpClient client = new HttpClient();
client.badCertificateCallback = ((X509Certificate cert, String host, int port) {
final isValidHost = host == "api.my_app";
return isValidHost;
});
Если у вас больше хостов, вы можете просто добавить туда новую проверку.
import 'package:http/io_client.dart';
import 'dart:io';
import 'package:http/http.dart';
import 'dart:async';
import 'dart:convert';
Future getAccessToken(String url) async {
try {
final ioc = new HttpClient();
ioc.badCertificateCallback =
(X509Certificate cert, String host, int port) => true;
final http = new IOClient(ioc);
http.post('url', body: {"email": "xyz@xyz.com", "password": "1234"}).then(
(response) {
print("Reponse status : ${response.statusCode}");
print("Response body : ${response.body}");
var myresponse = jsonDecode(response.body);
String token = myresponse["token"];
});
} catch (e) {
print(e.toString());
}
}
Проверьте дату и время устройства в настройках устройства. Дата и время устройства установлены на предыдущую дату.
Это для метода библиотеки http. вот что вам нужно сделать, чтобы включить эту опцию глобально в вашем проекте.
class MyHttpoverrides extends HttpOverrides{
@override
HttpClient createHttpClient(SecurityContext context){
return super.createHttpClient(context)
..badCertificateCallback = (X509Certificate cert, String host, int port)=>true;
}
}
//void main() => runApp(MyApp());
void main(){
HttpOverrides.global=new MyHttpoverrides();
runApp(MyApp());
}
для получения дополнительной информации: https://fluttercorner.com/certificate-verify-failed-unable-to-get-local-issuer-certificate-in-flutter/
Используя пакет Dio для запроса на моем локальном сервере с самоподписанным сертификатом, я предпочитаю разрешить определенный хост, а не все домены.
//import 'package:get/get.dart' hide Response; //<-- if you use get package
import 'package:dio/dio.dart';
void main(){
HttpOverrides.global = new MyHttpOverrides();
runApp(MyApp());
}
class MyHttpOverrides extends HttpOverrides{
@override
HttpClient createHttpClient(SecurityContext context){
return super.createHttpClient(context)
..badCertificateCallback = ((X509Certificate cert, String host, int port) {
final isValidHost = ["192.168.1.67"].contains(host); // <-- allow only hosts in array
return isValidHost;
});
}
}
// more example: https://github.com/flutterchina/dio/tree/master/example
void getHttp() async {
Dio dio = new Dio();
Response response;
response = await dio.get("https://192.168.1.67");
print(response.data);
}
Для тех, кому нужно игнорировать ошибки сертификата только для определенных вызовов, вы можете использовать
Однако нет необходимости использовать его глобально. Вы можете использовать его только для определенных вызовов, которые, как вы знаете, испытывают ошибки рукопожатия, заключив вызов в
class IgnoreCertificateErrorOverrides extends HttpOverrides{
@override
HttpClient createHttpClient(SecurityContext context){
return super.createHttpClient(context)
..badCertificateCallback = ((X509Certificate cert, String host, int port) {
return true;
});
}
}
Future<void> myNonSecurityCriticalApiCall() async {
await HttpOverrides.runWithHttpOverrides(() async {
String url = 'https://api.example.com/non/security/critical/service';
Response response = await get(url);
// ... do something with the response ...
}, IgnoreCertificateErrorOverrides());
}
В моем случае это внешний API, который имеет действующий сертификат SSL и работает в браузере, но по какой-то причине не будет работать в моем приложении Flutter.
Для всех, кто приземляется сюда, нужно решить проблему, а не просто все разрешить.
Для меня проблема решена на стороне сервера (как и должно быть) без изменения кода. Теперь все в порядке. Во всех других решениях проблема все еще существует (например, Почтальон запускается, но он отображает ошибку конфигурации на земном шаре рядом с состоянием ответа) Конфигурация - Centos / Apache / LetsEncrypt / Python3.8 / Django3.1.5 / Mod_wsgi /, но я предполагаю что решение действительно для большинства установок Apache / LetsEncrypt
Шаги для решения:
- Найдите строку «SSLCACertificateFile» на виртуальном хосте, который вы хотите настроить. Например:
SSLCACertificateFile /etc/httpd/conf/ssl.crt/my_ca.crt
- Загрузите https://letsencrypt.org/certs/lets-encrypt-r3-cross-signed.txt В конце /etc/httpd/conf/ssl.crt/my_ca.crt (после ----- КОНЕЦ СЕРТИФИКАТА -----) начните новую строку и вставьте из Let's-encrypt-r3-cross-signed.txt все, что указано ниже ----- BEGIN CERTIFICATE ----- (включая ----- BEGIN CERTIFICATE --- -)
- Сохраните /etc/httpd/conf/ssl.crt/my_ca.crt
- Перезапустите Apache httpd
Ссылки:https://access.redhat.com/solutions/43575 https://letsencrypt.org/certs
Также вы можете проверить действительность вашего сертификата на https://www.digicert.com/help/ .
Для меня это было потому, что я использую HTTPS, а API использует HTTP, поэтому я просто изменил его на HTTP, и он работает.
Эта проблема возникла с нами, поскольку мы не используем fullchain.pem, созданный с помощью Let's encrypt на nginx. После изменения, это устраняет эту проблему.
server {
listen 443 ssl;
ssl_certificate /var/www/letsencrypt/fullchain.pem;
Для Apache вам может потребоваться настроить
SSLCertificateChainFile
. Дополнительное обсуждение проблемы
https://github.com/flutter/flutter/issues/50699
Для меня это была проблема с эмулятором андроида.
I just created a new android emulator that fixed my problem.
Если вы используете библиотеку Dio, «DefaultHttpClientAdapter» устарел и не должен использоваться. Вместо этого используйте IOHttpClientAdapter.
сначала импортируйте вручную:
import 'package:dio/io.dart';
затем используйте «IOHttpClientAdapter» вместо «DefaultHttpClientAdapter»
(_dio.httpClientAdapter as IOHttpClientAdapter).onHttpClientCreate =
(HttpClient client) {
client.badCertificateCallback =
(X509Certificate cert, String host, int port) => true;
return client;
};
Что ж, я понял, что на самом деле корень проблемы был в рассинхронизации часов на тестовом устройстве ...
На самом деле в моем случае это исправило меня после того, как я обновил дату и время на своем компьютере. Может помочь кому-то, я думаю
Мне специально нужно было использовать lib/client.dart
Client
интерфейс для HTTP-вызовов (т.е. http.Client вместо HttpClient). Этого требовалChopperClient
(ссылка).
Так что я не мог пройти HttpClient
от lib/_http/http.dart
прямо в Чоппер. ChopperClient может получитьHttpClient
в конструкторе, завернутом в ioclient.IOClient
.
HttpClient webHttpClient = new HttpClient();
webHttpClient.badCertificateCallback = ((X509Certificate cert, String host, int port) => true);
dynamic ioClient = new ioclient.IOClient(webHttpClient);
final chopper = ChopperClient(
baseUrl: "https://example.com",
client: ioClient,
services: [
MfService.create()
],
converter: JsonConverter(),
);
final mfService = MfService.create(chopper);
Таким образом, вы можете временно игнорировать ошибку CERTIFICATE_VERIFY_FAILED в своих вызовах. Помните - это только для целей разработки. Не используйте это в производственной среде!
Это решение наконец-то сработало. Спасибо Миладу
final ioc = new HttpClient();
ioc.badCertificateCallback =
(X509Certificate cert, String host, int port) => true;
final http = new IOClient(ioc);
http.post(); //Your Get or Post Request
Использовать tls 1.3
URL запроса, нет проблем. пример
import 'dart:io';
main() async {
HttpClient client = new HttpClient();
// tls 1.2 error
// var request = await client.getUrl(Uri.parse('https://shop.io.mi-img.com/app/shop/img?id=shop_88f929c5731967cbc8339cfae1f5f0ec.jpeg'));
// tls 1.3 normal
var request = await client.getUrl(Uri.parse('https://ae01.alicdn.com/kf/Ud7cd28ffdf6e475c8dc382380d5d1976o.jpg'));
var response = await request.close();
print(response.headers);
client.close(force: true);
}
Я попробовал все вышеперечисленные решения, но ничего не помогло... Я понял, что решение заключается в ошибке с датой и часовым поясом мобильного устройства. используемый часовой пояс не совпадает с часовым поясом моей страны. Я изменил его, и это сработало. !!!
Если вы используете эмулятор. Поэтому убедитесь, что дата и время указаны правильно. Потому что в моем случае я нашел эту проблему.
Примечание. Если эта ошибка возникает не при попытке подключения к локальному SSL, исправьте ее правильно, а не просто используйте
Но... если вы столкнулись с этой проблемой из-за того, что хотите подключиться к локальной серверной части с самозаверяющим сертификатом, вы можете сделать следующее.
Вы можете использовать этот клиент для подключения к источникам, отличным от вашего локального сервера.
class AppHttpClient {
AppHttpClient({
Dio? client,
}) : client = client ?? Dio() {
if (kDebugMode) {
// this.client.interceptors.add(PrettyDioLogger());
}
}
final Dio client;
}
Вы можете использовать этот клиент для подключения к вашему локальному серверу. Убедитесь, что вы установили
class InternalApiHttpClient extends AppHttpClient {
ApiHttpClient({
super.client,
required this.baseUrl,
}) {
_allowBadDevelopmentCertificate();
}
void _allowBadDevelopmentCertificate() {
const flavor = String.fromEnvironment('FLAVOR');
if (flavor == 'development') {
final httpClientAdapter =
super.client.httpClientAdapter as DefaultHttpClientAdapter;
httpClientAdapter.onHttpClientCreate = (client) {
return client..badCertificateCallback = (cert, host, port) => true;
};
}
}
final Uri baseUrl;
}
Выполнение этого таким образом только подавляет сообщение о сертификате, когда вы находитесь в своей среде разработки, только при подключении к вашему локальному API. Любые другие запросы остаются нетронутыми и в случае сбоя должны решаться другим способом.
У нас такая же проблема. В результате мы добавили все ssl-токены в один файл на сервере и всё заработало.
В моем случае мне нужно было переделать ssl-сертификаты моего бэкэнда. Я сгенерировал сертификаты с помощью mkcert, и мне просто нужно было создать новые.
Если вы используете http.Client() из импорта «package:http/http.dart» как http; мы можем использовать эту функцию
import "dart:io";
import "package:http/io_client.dart";
IOClient getClient() {
final context = SecurityContext.defaultContext;
final HttpClient httpClient = HttpClient(context: context)
..badCertificateCallback = ((X509Certificate cert, String host, int port) => true);
final client = IOClient(httpClient);
client.get(Uri.parse(PSApp.config!.liveUrl!));
return client;
}
Я решил эту проблему в своем приложении: просто создайте вызов и вызовите его в main . вы можете увидеть здесь
import 'dart:io';
class MyHttpOverrides extends HttpOverrides{
@override
HttpClient createHttpClient(SecurityContext? context){
return super.createHttpClient(context)
..badCertificateCallback = (X509Certificate cert, String host, int port)=> true;
}
}
и
main() async {
WidgetsFlutterBinding.ensureInitialized();
await GetStorage.init();
HttpOverrides.global = MyHttpOverrides();
runApp(
Я исправил проблему, создав файл full_chain.crt.
Возможно, вы получили файл your_domain.crt и файл your_domain.ca-bundle. Теперь вам нужно объединить файл crt и файл ca-bundle для создания файла crt.
cat domain.crt domain.ca-bundle >> your_domain_full_chain.crt
Тогда вам просто нужно положить файл your_domain_full_chain.crt в nginx, и он начнет работать правильно.
Если другие предложения не помогли, есть другой способ. После обновления SSL-сертификата веб-сайта от goDaddy все запросы API https на устройстве Android возвращали это исключение:
I/flutter (23473): HandshakeException: Handshake error in client (OS Error:
I/flutter (23473): CERTIFICATE_VERIFY_FAILED: unable to get local issuer certificate(handshake.cc:393))
Это вызывает проблему только в приложении Flutter, работающем на Android. Приложение iOS по-прежнему работает без проблем.
Серверное решение без каких-либо изменений в приложении состоит в том, чтобы объединить файл сертификата вашего домена и файл цепочки центров сертификации и включить связанный файл сертификата в конфигурацию nginx.
Это происходит потому, что сертификат центра сертификации предоставляется в виде .pem-файла для использования в конфигурации vhost Apache2 следующим образом:
SSLCertificateChainFile /path/to/chain.pem
Однако для использования его в nginx нам нужно объединить их, как показано ниже:
cat example.org.crt chain.pem > bundle.crt
ssl_certificate bundle.crt
И используйте файл результатов в nginx.