网络安全配置实战:使用 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 包即使被反编译,也拿不到调试证书的信任配置。

配置调试证书的完整流程

我整理了一个标准流程,你照着做基本不会出错:

  1. 导出代理证书(Charles/Fiddler 的 .cer 文件)
  2. 放入 res/raw/ 目录(或者用 src="user" 直接信任用户证书)
  3. 编写 network_security_config.xml,指定信任的证书源
  4. 在 AndroidManifest.xml 中引用
  5. 验证配置是否生效

验证这一步很关键。我一般用两种方法:

  • 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 是证书固定的过期时间。一旦过了这个时间,固定策略就失效了。我建议至少配两个备用证书,防止主证书过期导致服务不可用。

⚠️ 警告: 证书固定是一把双刃剑。如果证书更换了但没更新配置,你的 App 就彻底连不上服务器了。我曾经见过一个线上事故,就是因为证书固定没配备用证书,导致用户大面积断连。

常见问题与避坑

我总结了一些实际开发中容易踩的坑:

问题 原因 解决方案
Charles 抓包显示 SSL 错误 用户证书未被信任 在 domain-config 中添加 <certificates src="user"/>
Release 包也能抓包 Debug 配置被误用到 Release 使用不同的配置文件,通过 buildTypes 区分
证书固定后无法访问 证书哈希值错误或过期 检查 SHA-256 值,确保至少有两个备用证书
明文 HTTP 请求被拒绝 cleartextTrafficPermitted 未开启 在 domain-config 中设置 cleartextTrafficPermitted="true"
💡 小提示: 获取证书的 SHA-256 哈希值可以用 OpenSSL:
openssl x509 -in certificate.cer -noout -fingerprint -sha256
记得把冒号去掉,然后 Base64 编码。

知识体系结构图

下面这张图帮你理清 network_security_config.xml 的核心逻辑:

network_security_config.xml 核心逻辑 network-security-config base-config / domain-config trust-anchors cleartextTrafficPermitted pin-set system / user / raw true / false SHA-256 哈希值

写在最后

说实话,network_security_config.xml 这个功能,Google 做得确实漂亮。它把网络安全配置从代码里抽离出来,变成了声明式的配置。你只需要关注「哪个环境该信任哪些证书」,剩下的交给系统处理。

我个人建议:Debug 包永远信任用户证书,这样 Charles、Fiddler 随便用。Release 包只信任系统证书,必要时加上证书固定。别为了省事把调试配置带到线上,那等于给攻击者开了后门。

嗯,今天就聊到这儿。配置调试证书这件事,说白了就是「信任管理」。你信任谁,谁就能看到你的数据。想清楚这一点,配置起来就游刃有余了。


公众号:蓝海资料掘金营,微信deep3321