Flutter graphql_flutter实现SSL Pinning校验不生效问题
Flutter graphql_flutter 集成SSL Pinning校验不生效问题
问题场景
需要在Flutter项目中借助graphql_flutter与http_certificate_pinning两个库实现SSL证书绑定(SSL Pinning)能力,初始实现代码如下:
import 'package:graphql_flutter/graphql_flutter.dart'; import 'package:http_certificate_pinning/http_certificate_pinning.dart'; class Service { final List<String> _allowedSHAFingerprints; late GraphQLClient _gqlClient; GraphQLClient get gqlClient => _gqlClient; late SecureHttpClient _secureHttpClient; SecureHttpClient get secureHttpClient => _secureHttpClient; Service(this._allowedSHAFingerprints) { _secureHttpClient = SecureHttpClient.build(_allowedSHAFingerprints); final httpLink = HttpLink( 'https://dummy.com/graphql/', httpClient: _secureHttpClient, ); _gqlClient = GraphQLClient( link: httpLink, cache: GraphQLCache(), ); } }
问题表现
- 配置错误的
allowedSHAFingerprints(合法证书SHA指纹列表)时,通过gqlClient发起GraphQL请求依然返回连接成功,SSL证书校验逻辑未触发 - 同一个
_secureHttpClient实例直接发起普通HTTP GET请求时,证书绑定校验可以正常工作,非法指纹场景会正常抛出异常
GraphQL请求调用代码如下:
Future<void> _gqlCall() async { try { final secureClient = GetIt.I<Service>().gqlClient; final options = QueryOptions( document: gql(homePageQuery), ); final result = await secureClient.query(options); if (!result.hasException) { _showSnackbar('GQL Success'); } else { throw Exception(); } } on Exception catch (_) { _showSnackbar('GQL Fail'); } }
可正常触发SSL校验的普通HTTP请求代码如下:
Future<void> _apiCall() async { try { final url = Uri.parse('https://dummy.com/ping'); final result = await GetIt.I<Service>().secureHttpClient.get(url); if (result.statusCode == 200) { _showSnackbar('API Success'); } else { throw Exception(); } } on Exception catch (_) { _showSnackbar('API Fail'); } }
问题原因
http_certificate_pinning旧版本的SecureHttpClient,证书校验逻辑仅写在非流式请求的_sendUnstreamed方法中。而graphql_flutter依赖的gql_http_link内部发起请求时,会直接调用客户端的send方法发送流式请求,完全绕过了_sendUnstreamed里的证书校验逻辑,导致SSL Pinning不生效。
直接调用secureHttpClient.get()这类便捷方法时,默认走非流式发送逻辑,所以校验可以正常触发。
修复方案
自定义包装类继承http.BaseClient,重写send方法,在所有请求发送前主动触发证书校验,再将包装后的客户端传给HttpLink即可。修复后的Service代码如下:
import 'dart:io'; import 'package:graphql_flutter/graphql_flutter.dart'; import 'package:http/http.dart' as http; import 'package:http_certificate_pinning/http_certificate_pinning.dart'; // 自定义包装客户端,覆盖所有请求场景的证书校验 class CertPinnedClient extends http.BaseClient { final SecureHttpClient _innerClient; final List<String> allowedFingerprints; final int timeout; CertPinnedClient({ required this.allowedFingerprints, this.timeout = 10000, }) : _innerClient = SecureHttpClient.build(allowedFingerprints); @override Future<http.StreamedResponse> send(http.BaseRequest request) async { // send阶段主动触发证书校验,覆盖流式请求场景 if (request.url.scheme == 'https') { final certCheck = await HttpCertificatePinning.check( serverURL: request.url.toString(), sha: SHA.SHA256, allowedSHAFingerprints: allowedFingerprints, timeout: timeout, ); if (!certCheck.isConnectionSecure) { throw const HandshakeException('SSL证书指纹校验不通过'); } } // 校验通过后发起实际请求 return _innerClient.send(request).timeout(Duration(milliseconds: timeout)); } } class Service { final List<String> _allowedSHAFingerprints; late GraphQLClient _gqlClient; GraphQLClient get gqlClient => _gqlClient; late CertPinnedClient _secureHttpClient; CertPinnedClient get secureHttpClient => _secureHttpClient; Service(this._allowedSHAFingerprints) { _secureHttpClient = CertPinnedClient(allowedFingerprints: _allowedSHAFingerprints); final httpLink = HttpLink( 'https://dummy.com/graphql/', httpClient: _secureHttpClient, ); _gqlClient = GraphQLClient( link: httpLink, cache: GraphQLCache(), ); } }
若升级http_certificate_pinning到最新版本后问题仍存在,使用上述自定义包装客户端的写法即可覆盖所有请求场景的证书校验,替换后无论是普通HTTP请求还是GraphQL请求,非法指纹场景都会直接抛出异常。
内容的提问来源于stack exchange,提问作者Serpentarius
相关产品推荐
相关产品推荐

