
1. 项目背景与核心诉求为什么我们还在讨论明文HTTP在2024年的今天当HTTPS已经成为移动应用开发的默认标准甚至各大应用商店和操作系统都将其作为强制要求时我们却要专门来讨论“允许明文HTTP通信”这个话题这听起来似乎有些不合时宜甚至像是在开倒车。但作为一名在Android开发一线摸爬滚打了十多年的老兵我必须告诉你现实情况远比想象中复杂。这个需求并非源于对安全性的漠视而是源于大量存量系统、特殊开发场景以及特定行业环境的真实困境。想象一下这样的场景你接手维护一个为工厂车间开发的内部设备管理App它需要与一台十几年前部署的、固件早已停止更新的老式PLC可编程逻辑控制器通信这台设备只支持HTTP协议。或者你正在开发一个用于本地网络调试的工具需要快速嗅探和分析局域网内其他设备发出的HTTP流量。又或者你的应用需要接入一个由第三方提供的、暂时还未升级HTTPS的测试环境API。在这些情况下生硬地要求“必须使用HTTPS”只会让项目停滞不前。Android系统为了推动全网HTTPS化从Android 9API级别28开始就默认禁止了应用使用明文HTTP流量。这意味着如果你的targetSdkVersion设置为28或更高应用将无法向非HTTPS的端点发起网络请求除非你显式地进行配置。这就是android:usesCleartextTraffic属性和网络安全配置Network Security Configuration文件登场的原因。它们不是用来鼓励使用HTTP而是为那些不得不使用HTTP的“例外情况”提供一条合规、可控的通道。本次讨论的核心就是如何在2024年的开发环境下安全、正确地为你的Android应用配置这条通道而不是简单粗暴地“一开了之”。2. 网络安全配置基础从android:usesCleartextTraffic到NSC在早期允许HTTP通信非常简单只需要在AndroidManifest.xml文件的application标签中添加一行配置即可application ... android:usesCleartextTraffictrue ... /application这行代码的作用是全局性的它告诉Android系统“我这个应用需要访问明文HTTP流量请放行”。在Android 6.0到8.1的时代这几乎是处理内部HTTP请求的标准做法。然而这种粗放式的配置带来了显著的安全风险。一旦开启应用内所有组件WebView、HttpURLConnection、OkHttp等对所有域名的HTTP请求都将被允许这无疑大大增加了应用遭受中间人攻击Man-in-the-Middle Attack的风险。因此从Android 9开始Google引入了更精细化的管理工具——网络安全配置Network Security Configuration 简称NSC。NSC允许开发者通过一个XML配置文件以声明式的方法定义应用的网络安全策略。它的核心思想是“最小权限原则”和“域隔离”最小权限只允许必要的域名或IP地址使用明文HTTP而不是全局放开。域隔离可以为不同的域名设置不同的安全策略。例如允许http://internal.company.com使用HTTP但强制https://api.public.com必须使用HTTPS并校验证书。当targetSdkVersion 28时即使你在AndroidManifest.xml中设置了android:usesCleartextTraffictrue”系统也会忽略此属性转而强制要求使用NSC文件来定义明文流量规则。如果你没有配置NSC那么所有的HTTP请求都将失败并可能抛出Cleartext traffic not permitted异常。这是很多开发者在升级Target SDK后遇到的第一个坑。所以在2024年正确的姿势是彻底放弃android:usesCleartextTraffictrue”全面拥抱网络安全配置NSC文件。即使你的应用暂时不需要HTTPS也应该通过NSC来明确声明你的HTTP访问范围这既是遵循最佳实践也是为应用未来的安全升级打下基础。3. 实战配置创建与应用网络安全配置文件接下来我们一步步地实现一个典型场景的配置允许应用访问特定的内部HTTP服务同时保持对其他所有流量的HTTPS要求。3.1 创建网络安全配置文件首先在你的Android项目res目录下新建一个xml文件夹如果不存在的话然后在该文件夹内创建一个名为network_security_config.xml的文件。这个文件名是自定义的但这是通用的命名约定。文件内容如下?xml version1.0 encodingutf-8? network-security-config !-- 基础配置默认情况下信任系统预装的CA证书并强制使用HTTPS -- base-config cleartextTrafficPermittedfalse trust-anchors certificates srcsystem / /trust-anchors /base-config !-- 针对特定域名的配置这里我们定义了一个“调试域” -- domain-config cleartextTrafficPermittedtrue !-- 允许使用明文HTTP的域名支持通配符* -- domain includeSubdomainstrue192.168.1.100/domain domain includeSubdomainstrueinternal.test.com/domain !-- 注意localhost和127.0.0.1在Android模拟器或设备本地有特殊处理通常不需要在此配置也能访问 -- /domain-config !-- 另一个例子为某个需要自定义证书的HTTPS域名配置 -- domain-config domain includeSubdomainstruesecure.corp.com/domain trust-anchors certificates srcraw/custom_ca_certificate / /trust-anchors /domain-config /network-security-config让我们拆解一下这个配置的关键部分base-config这是应用的默认网络安全配置。cleartextTrafficPermittedfalse表示默认禁止所有明文HTTP流量。certificates srcsystem /表示默认信任Android系统内置的证书颁发机构CA这是验证HTTPS证书有效性的基础。domain-config cleartextTrafficPermittedtrue这是一个域级别的覆盖配置。它为内部指定的一个或多个域名设置了不同的规则。cleartextTrafficPermittedtrue是关键它明确允许列在此标签内的域名使用HTTP协议。includeSubdomainstrue表示此规则也适用于该域名的所有子域名例如配置了test.com则api.test.com和www.test.com也适用。域名指定我们允许了两个地址使用HTTP。一个是IP地址192.168.1.100这在访问本地局域网设备时非常常见另一个是域名internal.test.com。重要提示配置IP地址时直接写IP即可不要加http://前缀。关于本地地址对于localhost、127.0.0.1或10.0.2.2模拟器访问主机这类环回地址Android系统通常有豁免即使在默认禁止明文的情况下应用也可以访问。但为了策略清晰和兼容性如果你遇到问题也可以将它们显式添加到domain-config中。3.2 在AndroidManifest中引用配置文件创建好配置文件后需要在AndroidManifest.xml中告诉应用使用它。在application标签内添加android:networkSecurityConfig属性进行关联manifest ... application android:networkSecurityConfigxml/network_security_config ... ... /application /manifest至此配置工作就完成了。你的应用现在拥有了一个清晰的网络安全策略默认情况下所有对外请求必须使用HTTPS但针对192.168.1.100和internal.test.com这两个特定的地址允许使用HTTP进行通信。3.3 验证配置是否生效配置完成后如何验证呢最直接的方法是发起一个HTTP请求到你所配置的域名。你可以使用OkHttp或HttpURLConnection写一个简单的测试代码。如果配置正确请求会成功如果失败通常会抛出异常并在Logcat中看到相关的网络错误信息。一个更系统的方法是检查应用在运行时加载的网络安全配置。你可以通过以下ADB命令来验证adb shell dumpsys package your.package.name | grep -A 50 “Network Security Config”这个命令会输出你的应用当前生效的网络安全配置详情你可以检查其中是否包含了你的domain-config规则。4. 高级场景与深度避坑指南基础的配置只能解决80%的问题剩下的20%往往隐藏在各种边界情况和细节之中。下面分享几个我踩过坑的高级场景和对应的解决方案。4.1 WebView中的明文HTTP访问如果你的应用使用WebView来加载本地HTML或访问内部HTTP服务器NSC配置同样适用但需要注意一个关键点WebView的兼容性行为。在Android 9及以上版本WebView默认遵守应用的全局网络安全配置。也就是说如果你按照上述方法配置了NSCWebView加载你允许的HTTP网址应该是没有问题的。但是存在一个历史遗留的“坑”在Android 9API 28且WebView版本低于某个特定版本时WebView可能会忽略NSC中针对localhost的配置。这通常发生在使用系统WebView且系统未更新的老旧设备上。虽然这种情况现在已不多见但为了绝对可靠特别是对于加载本地file://或http://localhost内容的Hybrid应用我建议采取双重保障在NSC中显式声明本地域尽管可能不是必须的domain-config cleartextTrafficPermittedtrue domain includeSubdomainstruelocalhost/domain /domain-config在代码中为WebView设置WebViewClient并覆盖shouldOverrideUrlLoading虽然这主要用于控制导航但在某些极端情况下也能起到作用。更直接的是确保你使用的第三方库或自定义的WebView都运行在主应用的进程上下文中以继承NSC配置。4.2 使用IP地址与域名混用的陷阱在配置domain时直接使用IP地址如192.168.1.100是允许的。但这里有一个非常重要的细节DNS解析的结果与配置的匹配是字面匹配。假设你的内部服务IP是192.168.1.100但你的代码中请求的URL是http://api.internal/并且api.internal通过本地DNS或/etc/hosts文件解析到了192.168.1.100。在这种情况下网络请求会失败因为NSC检查的是URL中的主机名host即api.internal而不是最终解析的IP地址。你的配置允许的是主机名为192.168.1.100的请求而不是解析到该IP的所有主机名。解决方案方案A推荐在代码中直接使用IP地址构造URL。这是最直接、最可靠的方式http://192.168.1.100/api/data。方案B在NSC配置文件中将所有可能用到的主机名域名都列出来。例如同时添加domain includeSubdomainstrueapi.internal/domain。方案C对于复杂的内部网络环境可以考虑在应用启动时通过代码动态修改网络安全配置但这涉及反射和更复杂的逻辑非必要不推荐。4.3 第三方库与底层网络组件的兼容性现代Android应用网络层大多使用OkHttp或Retrofit它们都很好地遵循系统的网络安全策略。但如果你集成了某些使用原生代码C/C或使用低级别Socket进行网络通信的第三方库例如某些游戏引擎、音视频SDK或物联网SDK情况可能会变得复杂。这些库可能绕过Java层的网络栈直接调用操作系统底层的BSD Socket接口。在这种情况下标准的NSC配置可能无法生效因为NSC的拦截点主要在Java网络库这一层。排查与解决思路查阅SDK文档首先确认该SDK是否对Android 9的明文HTTP限制有特殊说明。正规的SDK会提供配置指南。全局放行最后的手段如果确认是底层库的问题且该库只用于访问特定的安全内网环境你可以考虑在NSC中配置一个非常宽松的base-config。这是一个安全风险很高的操作务必谨慎评估network-security-config !-- 警告此配置允许所有明文流量仅用于测试或绝对可信的内部网络环境 -- base-config cleartextTrafficPermittedtrue trust-anchors certificates srcsystem / /trust-anchors /base-config /network-security-config隔离网络操作如果可能将必须使用HTTP的、与这类SDK交互的功能封装到一个独立的进程或使用android:process属性隔离的应用组件中。然后只为这个进程配置较宽松的NSC从而限制安全风险的影响范围。4.4 调试构建与发布构建的差异化配置在开发阶段我们可能需要访问本地的HTTP调试服务器而上线版本则必须严格禁止所有明文流量。手动修改配置文件很容易出错。最佳实践是利用Android构建系统Gradle的资源合并和变体Build Variants功能为debug和release构建不同的NSC文件。操作步骤保持src/main/res/xml/network_security_config.xml作为release版本的配置里面只包含必须的、安全的HTTP域名如果有的话或者完全禁止明文。在src/debug/res/xml/目录下创建同名的network_security_config.xml文件。Gradle在构建debug版本时会优先使用debug目录下的这个文件覆盖main中的配置。在debug版本的配置文件中你可以放心地添加你的本地开发服务器地址如domain includeSubdomainstrue10.0.2.2/domain用于模拟器访问主机或其他测试环境域名。这样你就能在开发时畅通无阻发布时自动收紧安全策略避免因疏忽将调试配置带到生产环境的安全事故。5. 安全红线何时绝对不应该允许明文HTTP尽管我们讨论了各种允许HTTP的“姿势”但必须划清一条绝对的安全红线。作为一名负责任的开发者你必须清楚在以下场景中允许明文HTTP是绝对不可接受的传输用户个人敏感信息包括但不限于密码、身份证号、银行卡号、生物特征信息、家庭住址、精确地理位置轨迹等。这些信息一旦在传输中被截获将直接导致用户隐私泄露和财产损失。涉及身份认证的请求例如登录、令牌Token刷新、会话保持等API。攻击者通过中间人攻击不仅可以窃取认证凭证还可能直接冒充用户进行操作。面向公网的生产环境服务任何部署在互联网上、可供公众访问的服务端接口都必须使用HTTPS。这是现代互联网应用不可妥协的底线。Let‘s Encrypt等机构提供免费的SSL/TLS证书成本已不再是借口。金融、支付、政务、医疗等强监管领域这些行业有明确的法律法规和行业标准如PCI DSS、HIPAA等强制要求使用加密通信违反规定不仅是不专业更可能涉及法律风险。允许HTTP通信本质上是在网络传输层放弃了机密性和完整性保护。你应当始终将其视为一个临时的、局部的、风险可控的例外而不是常态。在配置时要反复问自己这个HTTP端点是否在绝对可信的局域网内传输的数据是否非敏感是否有迫不得已的理由不能升级到HTTPS并且在项目文档和代码注释中明确记录这些例外配置的原因和范围以便未来的维护者知晓其中的安全权衡。6. 从明文HTTP到HTTPS的平滑迁移路线图讨论“允许明文HTTP”的终极目的是为了最终“消灭”它。对于任何长期项目都应该制定一个向HTTPS全面迁移的路线图。以下是一个可行的迁移计划评估与清单梳理应用所有网络请求端点区分出哪些是内部的、哪些是外部的哪些已支持HTTPS哪些仍在使用HTTP。使用NSC配置文件本身就是一个很好的清单工具。服务端升级这是迁移的基础。推动后端团队或服务提供商为所有公网和内部重要服务部署SSL/TLS证书。内部服务可以使用私有CA签发的证书。客户端适配证书信任如果使用私有CA证书需要在Android应用的NSC中配置trust-anchors来信任你的私有CA将证书放在res/raw/目录下引用。代码更新将代码中的HTTP URL统一替换为HTTPS URL。建议使用配置中心或依赖注入来管理Base URL避免硬编码。双轨运行与测试在服务端支持双协议HTTP/HTTPS运行一段时间。在客户端可以通过特性开关Feature Flag或构建变体让部分用户或测试版本先切换到HTTPS进行充分的功能和性能测试。收紧NSC策略当所有重要端点都确认HTTPS工作正常后开始逐步收紧客户端的NSC配置。首先将base-config中的cleartextTrafficPermitted设为false。然后逐个移除domain-config中允许HTTP的条目每移除一个都进行全面的回归测试。最终清理当确认所有流量都通过HTTPS后可以从代码库中删除所有用于允许HTTP的NSC配置条目并移除相关的特性开关和兼容代码。在AndroidManifest.xml中android:networkSecurityConfig属性可以指向一个只包含严格HTTPS策略的配置文件或者对于纯HTTPS应用甚至可以不指定该属性直接使用系统默认的严格策略。迁移过程中完善的监控和日志至关重要。你需要监控网络请求失败率、错误类型确保在切换过程中能快速发现问题并回滚。这个过程可能漫长但每一步都让应用变得更加安全、健壮。