Android 9.0+ 明文 HTTP 限制解决方案

背景

从 Android 9.0(API 28)开始,Google 默认禁止应用使用明文 HTTP 流量,所有网络请求必须用 HTTPS。这个改动提升了安全性,但开发和测试环境经常还在用 HTTP。这篇文章分享我处理这个问题的经验,包括 android:usesCleartextTrafficNetwork 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>
<!-- 内网 API 配置 -->
<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>

<!-- 公网 API:仅信任系统证书 -->
<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">
<!-- 主证书 SHA-256 哈希 -->
<pin digest="SHA-256">sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</pin>
<!-- 备份证书 SHA-256 哈希 -->
<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 webView = findViewById(R.id.webView);
WebSettings settings = webView.getSettings();

// Android 9.0+ 需要开启混合内容模式
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
# 从服务器获取证书并计算 SHA-256 哈希
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>
<!-- 调试配置:信任用户安装的证书(如 Charles/Fiddler) -->
<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>

<!-- 如有内网 API 需要特殊配置 -->
<!--
<domain-config>
<domain includeSubdomains="true">internal-api.example.com</domain>
<trust-anchors>
<certificates src="system"/>
<certificates src="@raw/company_ca"/>
</trust-anchors>
</domain-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
<!-- 正确:配置具体 IP -->
<domain includeSubdomains="false">192.168.1.100</domain>

<!-- 错误:不支持网段配置 -->
<!-- <domain>192.168.0.0/16</domain> -->

问题三:OkHttp 与系统配置不同步

如果应用使用 OkHttp 作为网络库,需要注意其配置可能独立于系统:

1
2
3
4
5
6
7
8
// OkHttp 需要单独配置明文通信
OkHttpClient client = new OkHttpClient.Builder()
.connectionSpecs(Arrays.asList(
ConnectionSpec.MODERN_TLS,
ConnectionSpec.COMPATIBLE_TLS,
ConnectionSpec.CLEARTEXT // 允许明文
))
.build();

小结

Android 网络安全配置的几条经验:

  1. 了解限制:Android 9.0+ 默认禁止明文 HTTP 流量,必须针对性配置
  2. 开发环境:使用 Network Security Config 精确控制允许明文通信的域名,避免全局开放
  3. 生产环境:完全禁用明文通信,所有 API 强制使用 HTTPS
  4. 内网场景:通过自定义 CA 证书配置,安全使用内网 HTTPS 服务
  5. 高级安全:考虑启用证书固定(Certificate Pinning),防止中间人攻击
  6. WebView 单独配置:WebView 的混合内容模式需要单独设置

合理的网络安全配置可以在保证开发效率的同时,确保应用的网络通信安全。