无需启用明文HTTP,将Flutter Android应用连接到Firebase Auth模拟器
问题背景
我开发了一款基于Firebase后端的Flutter应用,调试阶段使用Firebase模拟器测试。为了让物理设备能连接开发机上的模拟器,做了以下配置:
- 在
firebase.json中设置模拟器的host为0.0.0.0,避免硬编码可变的本地IP - 在
main.dart的kDebugMode分支中指定模拟器的局域网IP地址(通过Dart环境变量传递)
测试中发现:
- Web版本能正常与Auth模拟器通信
- Android物理设备或模拟器运行调试版本时,调用
firebase_ui_auth的登录界面会抛出错误:com.google.firebase.FirebaseException: An internal error has occurred. [ Cleartext HTTP traffic to <my local ip> not permitted ] - 绕过认证功能后,Android应用可以正常连接Firestore模拟器
已知的常规解决方案是全局启用明文HTTP,或仅允许特定IP的明文流量,但我不想为本地测试全局开启明文,也不愿在Android XML中硬编码可变的本地IP,希望找到替代方案,比如让SDK通过HTTPS连接模拟器。目前临时方案是仅在debug版本的Android清单(android/app/src/debug)中全局启用明文HTTP。
替代方案
方案1:用HTTPS代理转发模拟器流量
通过搭建本地HTTPS代理,将Firebase Auth模拟器的HTTP请求转成HTTPS,这样Android应用就不会触发明文流量限制。
步骤:
生成自签名证书
用openssl生成有效期1年的自签名证书:openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes填写信息时,Common Name可以设为自定义的本地域名,比如
firebase-emulator.local。搭建HTTPS代理
用Caddy工具快速配置代理(无需复杂配置),创建Caddyfile:https://firebase-emulator.local { reverse_proxy http://localhost:9099 # Firebase Auth模拟器默认端口 tls cert.pem key.pem }启动Caddy后,访问
https://firebase-emulator.local就会转发到本地的Auth模拟器。配置Android信任自签名证书
- 将生成的
cert.pem重命名为firebase_emulator_cert.pem,放到android/app/src/debug/res/raw/目录下 - 在
android/app/src/debug/res/xml/network_security_config.xml中添加信任配置:<?xml version="1.0" encoding="utf-8"?> <network-security-config> <base-config> <trust-anchors> <certificates src="@raw/firebase_emulator_cert"/> <certificates src="system"/> </trust-anchors> </base-config> </network-security-config> - 在debug版本的
AndroidManifest.xml中指定该配置:<application ... android:networkSecurityConfig="@xml/network_security_config">
- 将生成的
修改Flutter代码中的模拟器地址
在kDebugMode分支中,将Auth模拟器地址改为https://firebase-emulator.local,同时在物理设备的hosts文件中映射该域名到开发机的局域网IP(或者用本地DNS服务解析)。
方案2:动态配置允许的IP(无需硬编码)
利用Android构建系统的变量替换功能,结合Flutter环境变量动态设置允许明文流量的IP,避免硬编码。
步骤:
创建debug专用的网络安全配置
在android/app/src/debug/res/xml/network_security_config.xml中添加:<?xml version="1.0" encoding="utf-8"?> <network-security-config> <domain-config cleartextTrafficPermitted="true"> <domain includeSubdomains="true">${LOCAL_IP}</domain> </domain-config> </network-security-config>在build.gradle中注入环境变量
修改android/app/build.gradle的buildTypes块,从环境变量中获取LOCAL_IP并替换到资源文件:android { ... buildTypes { debug { resValue "string", "LOCAL_IP", System.getenv("LOCAL_IP") ?: "10.0.2.2" } } }这里默认值
10.0.2.2是Android模拟器访问主机的默认IP。运行Flutter时传递局域网IP
启动应用时通过环境变量指定你的开发机局域网IP:LOCAL_IP=192.168.1.100 flutter run --debug这样debug版本的Android应用只会允许该IP的明文流量,比全局启用更安全。
额外说明
Firestore模拟器能正常连接的原因是:Firebase Firestore SDK在调试模式下,内部对模拟器连接做了特殊处理,自动允许明文流量;而firebase_ui_auth依赖的Auth SDK没有这个逻辑,因此触发了Android的明文流量限制。
内容的提问来源于stack exchange,提问作者Anakhand

