如何在Flutter的HTTP插件中为所有API请求配置SSL固定
使用Flutter HTTP插件实现SSL固定的方法与示例
核心思路
SSL固定的本质是在请求时校验服务器证书的合法性,避免中间人攻击。对于HTTP插件,我们需要自定义BaseClient,通过重写证书校验逻辑实现固定。
步骤1:获取服务器证书的SHA-256哈希值
先拿到你API服务器证书的指纹,用以下命令(替换成你的域名):
openssl s_client -connect your-api-domain.com:443 | openssl x509 -noout -fingerprint -sha256
输出结果类似SHA256 Fingerprint=AB:CD:EF...,去掉所有冒号,得到纯大写的哈希字符串备用。
步骤2:基于HTTP插件实现指纹校验
自定义客户端类,重写证书校验逻辑:
import 'dart:convert'; import 'dart:io'; import 'package:crypto/crypto.dart'; import 'package:http/http.dart' as http; // 替换成你的服务器证书SHA-256哈希(去掉冒号) const String _trustedCertSha256 = "ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890"; class SslPinningClient extends http.BaseClient { final http.Client _inner = http.Client(); @override Future<http.StreamedResponse> send(http.BaseRequest request) async { if (request.url.scheme == 'https') { final client = HttpClient(); // 自定义证书校验逻辑 client.badCertificateCallback = (X509Certificate cert, String host, int port) { // 计算当前证书的SHA-256哈希 final certSha256 = sha256.convert(cert.der).toString().toUpperCase(); // 对比信任的哈希值,一致则通过校验 return certSha256 == _trustedCertSha256; }; try { final httpRequest = await client.openUrl(request.method, request.url); // 复制原请求的头信息 request.headers.forEach((key, value) => httpRequest.headers.add(key, value)); // 写入请求体 if (request is http.Request && request.body.isNotEmpty) { httpRequest.write(request.body); } final httpResponse = await httpRequest.close(); // 转换为HTTP插件的响应格式 final bodyStream = httpResponse.transform(utf8.decoder); final body = await bodyStream.join(); return http.StreamedResponse( Stream.value(utf8.encode(body)), httpResponse.statusCode, headers: httpResponse.headers, reasonPhrase: httpResponse.reasonPhrase, ); } finally { client.close(); } } // 非HTTPS请求直接用原客户端处理 return _inner.send(request); } } // 使用示例 void main() async { final client = SslPinningClient(); try { final response = await client.get(Uri.parse('https://your-api-domain.com/api/test')); print('响应状态码: ${response.statusCode}'); print('响应内容: ${response.body}'); } catch (e) { print('请求失败: $e'); } finally { client.close(); } }
可选:嵌入完整证书文件校验
如果需要更严格的校验,可以直接把服务器证书(.pem格式)放到项目assets中,对比完整证书内容:
import 'dart:convert'; import 'dart:io'; import 'package:flutter/services.dart'; import 'package:http/http.dart' as http; class SslPinningClient extends http.BaseClient { final http.Client _inner = http.Client(); late List<int> _trustedCertBytes; SslPinningClient() { _loadTrustedCert(); } // 从assets加载信任的证书文件(需在pubspec.yaml中配置assets路径) Future<void> _loadTrustedCert() async { final certString = await rootBundle.loadString('assets/trusted_cert.pem'); _trustedCertBytes = utf8.encode(certString); } @override Future<http.StreamedResponse> send(http.BaseRequest request) async { if (request.url.scheme == 'https') { final client = HttpClient(); client.badCertificateCallback = (X509Certificate cert, String host, int port) { // 对比证书的原始字节内容 return jsonEncode(cert.der) == jsonEncode(_trustedCertBytes); }; // 后续请求逻辑和指纹校验版本一致,此处省略... try { final httpRequest = await client.openUrl(request.method, request.url); request.headers.forEach((key, value) => httpRequest.headers.add(key, value)); if (request is http.Request && request.body.isNotEmpty) { httpRequest.write(request.body); } final httpResponse = await httpRequest.close(); final bodyStream = httpResponse.transform(utf8.decoder); final body = await bodyStream.join(); return http.StreamedResponse( Stream.value(utf8.encode(body)), httpResponse.statusCode, headers: httpResponse.headers, reasonPhrase: httpResponse.reasonPhrase, ); } finally { client.close(); } } return _inner.send(request); } }
内容的提问来源于stack exchange,提问作者Mona Yalda
相关产品推荐
相关产品推荐

