背景
从 Android 9.0(API 28)开始,Google 默认禁止应用使用明文 HTTP 流量,所有网络请求必须用 HTTPS。这个改动提升了安全性,但开发和测试环境经常还在用 HTTP。这篇文章分享我处理这个问题的经验,包括 android:usesCleartextTraffic 和 Network Security Config 两种配置方案,以及生产环境的 TLS 配置建议。
Android 网络安全策略演进
各版本网络安全行为对比
| Android 版本 |
API 级别 |
默认 HTTP 行为 |
配置方式 |
| Android 8.1 及以下 |
≤27 |
允许明文 HTTP |
无需配置 |
| Android 9.0 |
28 |
默认禁止 HTTP |
usesCleartextTraffic / NetworkSecurityConfig |
| Android 10.0+ |
29+ |
强化 TLS 1.3 支持 |
同上 |
错误现象
当应用尝试在 Android 9.0+ 设备上发送 HTTP 请求时,会抛出以下异常:
1 2 3 4 5 6 7
| java.io.IOException: Cleartext HTTP traffic to xxx.xxx.xxx.xxx not permitted
// OkHttp 报错 java.net.UnknownServiceException: CLEARTEXT communication to 10.0.0.5 not permitted by network security policy
// WebView 报错 Webpage not available: net::ERR_CLEARTEXT_NOT_PERMITTED
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| ┌─────────────────────────────────────────────────────────────┐ │ Android 9.0+ HTTP 请求拦截流程 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ App 发起 HTTP 请求 │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────┐ │ │ │ Network Security Policy 检查 │ │ │ │ │ │ │ │ 目标域名是否在 cleartext 白名单中? │ │ │ │ │ │ │ │ │ 是 ──┘ │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ 允许明文请求 ──► 继续 HTTP 通信 │ │ │ │ │ │ │ │ 否 ────────────────────────────────┐ │ │ │ │ │ │ │ │ │ │ ▼ ▼ │ │ │ │ 检查是否允许全局明文 │ │ │ │ │ │ │ │ │ │ │ 是 ──┘ │ │ │ │ │ │ │ │ │ │ │ ▼ ▼ │ │ │ │ 允许明文请求 抛出 IOException │ │ │ 继续 HTTP 通信 Cleartext not permitted │ │ └─────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘
|
方案一:简单配置(usesCleartextTraffic)
全局允许明文流量
在 AndroidManifest.xml 的 <application> 标签中添加 android:usesCleartextTraffic="true":
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.myapp">
<uses-permission android:name="android.permission.INTERNET" />
<application android:name=".MyApplication" android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" android:usesCleartextTraffic="true">
<activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
|
优缺点分析
| 优点 |
缺点 |
| 配置简单,一行代码解决 |
安全性最低,允许所有 HTTP 请求 |
| 兼容所有 Android 版本 |
不符合 Google Play 安全审核要求 |
| 适合快速开发和测试 |
生产环境强烈不推荐 |
方案二:网络安全配置(推荐)
Network Security Config 是 Android 7.0(API 24)引入的 XML 配置机制,允许更精细地控制应用的网络安全策略。
基础配置步骤
1. 创建配置文件
在 res/xml/ 目录下创建 network_security_config.xml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <?xml version="1.0" encoding="utf-8"?> <network-security-config> <domain-config cleartextTrafficPermitted="true"> <domain includeSubdomains="true">api.example.com</domain> <domain includeSubdomains="true">10.0.0.5</domain> <domain includeSubdomains="true">192.168.1.100</domain> <domain includeSubdomains="true">localhost</domain> </domain-config>
<base-config cleartextTrafficPermitted="false"> <trust-anchors> <certificates src="system"/> </trust-anchors> </base-config> </network-security-config>
|
2. 在 Manifest 中引用
1 2 3 4 5
| <application android:networkSecurityConfig="@xml/network_security_config" ... > ... </application>
|
进阶配置场景
场景一:调试环境允许 HTTP,生产环境强制 HTTPS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <?xml version="1.0" encoding="utf-8"?> <network-security-config> <base-config cleartextTrafficPermitted="false"> <trust-anchors> <certificates src="system"/> </trust-anchors> </base-config>
<domain-config cleartextTrafficPermitted="true"> <domain includeSubdomains="true">dev.example.com</domain> <domain includeSubdomains="true">192.168.0.0/16</domain> <domain includeSubdomains="true">10.0.0.0/8</domain> </domain-config> </network-security-config>
|
场景二:配置自定义 CA 证书(内网 HTTPS)
当服务器使用自签名证书或企业内部 CA 时:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <?xml version="1.0" encoding="utf-8"?> <network-security-config> <domain-config> <domain includeSubdomains="true">internal.company.com</domain>
<trust-anchors> <certificates src="system"/> <certificates src="@raw/my_ca" /> </trust-anchors>
<cleartextTrafficPermitted>true</cleartextTrafficPermitted> </domain-config>
<base-config cleartextTrafficPermitted="false"> <trust-anchors> <certificates src="system"/> </trust-anchors> </base-config> </network-security-config>
|
将 CA 证书文件 my_ca.crt 放入 res/raw/ 目录。
场景三:证书固定(Certificate Pinning)
防止中间人攻击的高级安全配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <?xml version="1.0" encoding="utf-8"?> <network-security-config> <domain-config> <domain includeSubdomains="true">api.example.com</domain>
<pin-set expiration="2025-12-31"> <pin digest="SHA-256">sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</pin> <pin digest="SHA-256">sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=</pin> </pin-set>
<trust-anchors> <certificates src="system"/> </trust-anchors> <cleartextTrafficPermitted>false</cleartextTrafficPermitted> </domain-config> </network-security-config>
|
配置文件结构说明
| 标签 |
作用 |
属性 |
<network-security-config> |
根节点 |
- |
<base-config> |
默认全局配置 |
cleartextTrafficPermitted |
<domain-config> |
针对特定域名的配置 |
cleartextTrafficPermitted |
<domain> |
指定域名 |
includeSubdomains |
<trust-anchors> |
定义信任的证书源 |
- |
<certificates> |
证书来源 |
src="system" / src="user" / src="@raw/xxx" |
<pin-set> |
证书固定集合 |
expiration |
<pin> |
单个证书哈希 |
digest |
<debug-overrides> |
调试配置覆盖 |
- |
WebView 明文通信配置
WebView 独立配置
如果应用中使用了 WebView 加载 HTTP 页面,需要额外配置:
1 2 3 4 5 6 7 8
| WebView webView = findViewById(R.id.webView); WebSettings settings = webView.getSettings();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { settings.setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE); }
|
混合内容模式选项:
| 模式 |
说明 |
MIXED_CONTENT_ALWAYS_ALLOW |
允许所有混合内容(不安全) |
MIXED_CONTENT_NEVER_ALLOW |
禁止所有混合内容(最安全) |
MIXED_CONTENT_COMPATIBILITY_MODE |
兼容模式(推荐) |
开发调试技巧
获取证书 SHA-256 哈希
1 2 3 4 5 6
| openssl s_client -connect api.example.com:443 -servername api.example.com < /dev/null 2>/dev/null | \ openssl x509 -in /dev/stdin -pubkey -noout | \ openssl pkey -pubin -outform der | \ openssl dgst -sha256 -binary | \ openssl enc -base64
|
调试配置(仅开发环境)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <?xml version="1.0" encoding="utf-8"?> <network-security-config> <debug-overrides> <trust-anchors> <certificates src="user"/> <certificates src="system"/> </trust-anchors> </debug-overrides>
<base-config cleartextTrafficPermitted="false"> <trust-anchors> <certificates src="system"/> </trust-anchors> </base-config> </network-security-config>
|
注意:<debug-overrides> 仅在 android:debuggable="true" 的应用中生效,发布版本自动失效。
生产环境最佳实践
安全等级递进方案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| ┌─────────────────────────────────────────────────────────────┐ │ 生产环境网络安全等级 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ Level 1:基础安全 │ │ ───────────────── │ │ • 所有 API 使用 HTTPS │ │ • Network Security Config 禁止明文通信 │ │ • 信任系统证书 │ │ │ │ Level 2:增强安全(推荐) │ │ ───────────────────── │ │ • 启用证书固定(Certificate Pinning) │ │ • 配置备份证书哈希 │ │ • 监控证书到期时间 │ │ │ │ Level 3:最高安全 │ │ ───────────────── │ │ • 仅信任指定 CA 证书 │ │ • 完全禁用明文通信 │ │ • WebView 禁止混合内容 │ │ • 定期安全审计 │ │ │ └─────────────────────────────────────────────────────────────┘
|
推荐的生产配置模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <?xml version="1.0" encoding="utf-8"?> <network-security-config> <base-config cleartextTrafficPermitted="false"> <trust-anchors> <certificates src="system"/> </trust-anchors> </base-config>
</network-security-config>
|
服务器端 HTTPS 配置检查清单
| 检查项 |
要求 |
| TLS 版本 |
最低 TLS 1.2,推荐 TLS 1.3 |
| 证书有效期 |
在有效期内 |
| 证书链完整 |
包含中间证书 |
| HSTS 头 |
Strict-Transport-Security |
| 加密套件 |
禁用弱加密算法 |
常见问题排查
问题一:配置后仍报错
可能原因:缓存未清理或配置未生效。
1 2 3
| ./gradlew clean ./gradlew assembleDebug
|
问题二:IP 地址访问失败
Network Security Config 中配置 IP 地址时,不能配置 CIDR 网段,只能配置具体 IP:
1 2 3 4 5
| <domain includeSubdomains="false">192.168.1.100</domain>
|
问题三:OkHttp 与系统配置不同步
如果应用使用 OkHttp 作为网络库,需要注意其配置可能独立于系统:
1 2 3 4 5 6 7 8
| OkHttpClient client = new OkHttpClient.Builder() .connectionSpecs(Arrays.asList( ConnectionSpec.MODERN_TLS, ConnectionSpec.COMPATIBLE_TLS, ConnectionSpec.CLEARTEXT )) .build();
|
小结
Android 网络安全配置的几条经验:
- 了解限制:Android 9.0+ 默认禁止明文 HTTP 流量,必须针对性配置
- 开发环境:使用
Network Security Config 精确控制允许明文通信的域名,避免全局开放
- 生产环境:完全禁用明文通信,所有 API 强制使用 HTTPS
- 内网场景:通过自定义 CA 证书配置,安全使用内网 HTTPS 服务
- 高级安全:考虑启用证书固定(Certificate Pinning),防止中间人攻击
- WebView 单独配置:WebView 的混合内容模式需要单独设置
合理的网络安全配置可以在保证开发效率的同时,确保应用的网络通信安全。