网络安全配置实战:使用 network_security_config.xml 配置调试证书
说实话,Android 的网络安全配置是我这几年觉得最实用的功能之一。以前调试 HTTPS 接口,动不动就要改系统证书、root 手机,麻烦得要命。后来 Google 在 Android 7.0 推出了 network_security_config.xml,这玩意儿简直就是调试人员的福音。
今天我就带你彻底搞懂它。说白了,就是通过一个 XML 文件,告诉系统:「哪些证书我信,哪些我不信」。你可以针对不同环境、不同域名,灵活配置。
为什么需要这个配置?
先说说痛点。我在项目中遇到过好几次这样的情况:
- 测试环境用的是自签名证书,但 Release 包只认正式 CA 证书
- Debug 包需要信任 Charles 或 Fiddler 的代理证书
- 某些老接口还在用 HTTP,但新接口强制 HTTPS
你想想看,如果没有 network_security_config.xml,你只能改代码、改 Gradle 配置,甚至要打两个不同的 APK。有了它,一切变得优雅。
基本配置结构
先看一个最基础的例子。这个配置告诉系统:所有域名的 HTTPS 连接,都信任系统预装证书和用户安装的证书。
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config>
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</base-config>
</network-security-config>
嗯,这里要注意:src="user" 表示信任用户安装的证书。默认情况下,Android 7.0+ 是不信任用户证书的。加上这一行,Charles 的证书就能被识别了。
调试证书的实战配置
我个人习惯的做法是:Debug 包信任用户证书,Release 包只信任系统证书。这样既方便调试,又保证安全。
来看一个完整的例子:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="false">
<domain includeSubdomains="true">api.example.com</domain>
<trust-anchors>
<certificates src="system" />
</trust-anchors>
</domain-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">test.example.com</domain>
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</domain-config>
</network-security-config>
这个配置做了两件事:
- 生产环境(api.example.com):只信任系统证书,不允许明文 HTTP
- 测试环境(test.example.com):信任系统证书 + 用户证书,允许 HTTP
我曾经因为测试环境配错了证书,排查了整整一个下午。后来发现是用户证书没被信任,Charles 抓包一直报 SSL 错误。从那以后,我每次配完都会用 adb shell dumpsys 验证一下。
Debug 与 Release 的差异化配置
这里有个小技巧:你可以为 Debug 和 Release 使用不同的配置文件。在 build.gradle 里这样写:
android {
...
buildTypes {
debug {
manifestPlaceholders = [networkSecurityConfig: "@xml/network_security_config_debug"]
}
release {
manifestPlaceholders = [networkSecurityConfig: "@xml/network_security_config_release"]
}
}
}
然后在 AndroidManifest.xml 里引用占位符:
<application
android:networkSecurityConfig="${networkSecurityConfig}"
...>
这样 Debug 包和 Release 包就各自用各自的配置了。Release 包即使被反编译,也拿不到调试证书的信任配置。
配置调试证书的完整流程
我整理了一个标准流程,你照着做基本不会出错:
- 导出代理证书(Charles/Fiddler 的 .cer 文件)
- 放入 res/raw/ 目录(或者用
src="user"直接信任用户证书) - 编写 network_security_config.xml,指定信任的证书源
- 在 AndroidManifest.xml 中引用
- 验证配置是否生效
验证这一步很关键。我一般用两种方法:
- 用
adb shell dumpsys netpolicy查看当前应用的网络安全策略 - 直接抓包看能不能抓到 HTTPS 请求
高级用法:固定证书(Certificate Pinning)
如果你对安全性要求更高,可以固定证书。说白了就是只信任某一张特定的证书,其他的一概不认。
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config>
<domain includeSubdomains="true">secure.example.com</domain>
<pin-set expiration="2025-12-31">
<pin digest="SHA-256">47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=</pin>
<pin digest="SHA-256">Ggx9e3fHp8a5vB7cD2eF4gH6iJ8kL0mN2oP4qR6sT8u=</pin>
</pin-set>
</domain-config>
</network-security-config>
这里要注意:expiration 是证书固定的过期时间。一旦过了这个时间,固定策略就失效了。我建议至少配两个备用证书,防止主证书过期导致服务不可用。
常见问题与避坑
我总结了一些实际开发中容易踩的坑:
| 问题 | 原因 | 解决方案 |
|---|---|---|
| Charles 抓包显示 SSL 错误 | 用户证书未被信任 | 在 domain-config 中添加 <certificates src="user"/> |
| Release 包也能抓包 | Debug 配置被误用到 Release | 使用不同的配置文件,通过 buildTypes 区分 |
| 证书固定后无法访问 | 证书哈希值错误或过期 | 检查 SHA-256 值,确保至少有两个备用证书 |
| 明文 HTTP 请求被拒绝 | cleartextTrafficPermitted 未开启 | 在 domain-config 中设置 cleartextTrafficPermitted="true" |
openssl x509 -in certificate.cer -noout -fingerprint -sha256记得把冒号去掉,然后 Base64 编码。
知识体系结构图
下面这张图帮你理清 network_security_config.xml 的核心逻辑:
写在最后
说实话,network_security_config.xml 这个功能,Google 做得确实漂亮。它把网络安全配置从代码里抽离出来,变成了声明式的配置。你只需要关注「哪个环境该信任哪些证书」,剩下的交给系统处理。
我个人建议:Debug 包永远信任用户证书,这样 Charles、Fiddler 随便用。Release 包只信任系统证书,必要时加上证书固定。别为了省事把调试配置带到线上,那等于给攻击者开了后门。
嗯,今天就聊到这儿。配置调试证书这件事,说白了就是「信任管理」。你信任谁,谁就能看到你的数据。想清楚这一点,配置起来就游刃有余了。
公众号:蓝海资料掘金营,微信deep3321