如何为本地SignalR Web服务器启用SSL,解决Flutter连接异常?
初始连接尝试及错误
尝试在Flutter中通过以下代码连接本地SignalR服务器:
final connection = HubConnectionBuilder().withUrl('https://localhost:44320/chat-hub', HttpConnectionOptions( logging: (level, message) => print(message), )).build(); await connection.start(); connection.on('ReceiveMessage', (message) { print(message.toString()); });
握手成功并收到服务器消息后,程序崩溃,报错:
[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: The remote computer refused the network connection.
#0 IOClient.send (package:http/src/io_client.dart:94:7)
#1 BaseClient._sendUnstreamed (package:http/src/base_client.dart:93:32)
#2 _withClient (package:http/http.dart:166:12)
#3 _MainPageState.initState.(package:multipanel_chatwoot/presentation/pages/home_page.dart:22:24)
替换为IP地址后的错误
将localhost替换为本地IPhttps://192.168.1.39:44320/chat-hub后,协商请求出现证书验证错误:
flutter: Failed to complete negotiation with the server: HandshakeException: Handshake error in client (OS Error:
CERTIFICATE_VERIFY_FAILED: Hostname mismatch(../../third_party/boringssl/src/ssl/handshake.cc:393))
flutter: Failed to start the connection: HandshakeException: Handshake error in client (OS Error:
CERTIFICATE_VERIFY_FAILED: Hostname mismatch(../../third_party/boringssl/src/ssl/handshake.cc:393))
flutter: HubConnection failed to start successfully because of error '{HandshakeException: Handshake error in client (OS Error:
CERTIFICATE_VERIFY_FAILED: Hostname mismatch(../../third_party/boringssl/src/ssl/handshake.cc:393)).toString()}'.
改用HTTP协议后的错误
改用HTTP协议连接时,报错:
flutter: Failed to complete negotiation with the server: Connection closed before full header was received
添加证书忽略代码后的错误
添加本地开发用的证书忽略代码:
// in main() HttpOverrides.global = MyHttpOverrides(); class MyHttpOverrides extends HttpOverrides{ @override HttpClient createHttpClient(SecurityContext? context){ return super.createHttpClient(context) ..badCertificateCallback = (X509Certificate cert, String host, int port)=> true; } }
后出现错误:
flutter: Failed to start the connection: Exception: Unexpected status code returned from negotiate '400'
尝试跳过协商后的错误
在HttpConnectionOptions中添加skipNegotiation: true,报错:
flutter: Failed to start the connection: Exception: Negotiation can only be skipped when using the WebSocket transport directly.
解决方案:为本地SignalR服务器配置有效SSL证书
1. 生成信任的本地SSL证书
如果是ASP.NET Core SignalR服务器,可使用dotnet dev-certs工具生成并信任本地证书:
- 生成证书:
dotnet dev-certs https -ep $HOME/.aspnet/https/aspnetapp.pfx -p <自定义密码> - 信任证书:
Windows/macOS会自动弹出信任确认窗口,按提示完成操作即可。dotnet dev-certs https --trust
若需要包含本地IP和localhost的证书(避免主机名不匹配),可使用OpenSSL生成带SAN字段的证书:
openssl req -x509 -newkey rsa:4096 -sha256 -days 365 -nodes \ -keyout localhost.key -out localhost.crt -subj "/CN=localhost" \ -addext "subjectAltName=DNS:localhost,IP:192.168.1.39"
再转换为PFX格式供Kestrel使用:
openssl pkcs12 -export -out aspnetapp.pfx -inkey localhost.key -in localhost.crt
2. 配置SignalR服务器使用证书
在ASP.NET Core项目的appsettings.json中添加证书配置:
{ "Kestrel": { "Certificates": { "Default": { "Path": "aspnetapp.pfx", "Password": "<之前设置的密码>" } } } }
或者在Program.cs中直接配置Kestrel:
builder.WebHost.ConfigureKestrel(serverOptions => { serverOptions.ListenAnyIP(44320, options => { options.UseHttps("aspnetapp.pfx", "<密码>"); }); });
3. Flutter端正确加载信任证书
- 将生成的
localhost.crt放入Flutter项目的assets目录,在pubspec.yaml中声明:assets: - assets/localhost.crt - 编写代码加载证书到SecurityContext:
Future<SecurityContext> getSecurityContext() async { final certBytes = await rootBundle.load('assets/localhost.crt'); final context = SecurityContext.defaultContext; context.setTrustedCertificatesBytes(certBytes.buffer.asUint8List()); return context; } - 连接时使用该SecurityContext:
final connection = HubConnectionBuilder().withUrl( 'https://192.168.1.39:44320/chat-hub', HttpConnectionOptions( logging: (level, message) => print(message), httpClientAdapter: IOHttpClientAdapter( createHttpClient: () async => HttpClient(context: await getSecurityContext()), ), ), ).build();
4. 本地开发临时绕过证书验证(仅测试用)
如果不想配置证书,可直接指定WebSocket传输并跳过协商,同时保留证书忽略代码:
final connection = HubConnectionBuilder().withUrl( 'wss://192.168.1.39:44320/chat-hub', HttpConnectionOptions( logging: (level, message) => print(message), skipNegotiation: true, transport: HttpTransportType.webSockets, ), ).build();
注意:此方式仅适用于本地开发,生产环境绝对禁止使用。
内容的提问来源于stack exchange,提问作者Royal_Scribblz

