4、网络安全防护:HTTPS配置、证书锁定、OkHttp安全配置、WebView安全、网络安全配置
聊到网络安全,我见过太多开发者觉得「只要用了 HTTPS 就万事大吉」。说实话,这个想法挺危险的。HTTPS 只是基础,真正的防护要从配置细节做起。今天我把这些年踩过的坑和积累的经验,一次性讲清楚。
4.1 HTTPS 配置:别只改个 URL 就完事
很多人以为 HTTPS 配置就是把 http:// 改成 https://。嗯,如果真这么简单,那网络安全工程师怕是要失业了。
我个人习惯在 Android 的 network_security_config.xml 里明确指定哪些域名走 HTTPS,哪些可以降级。你想想看,如果不做限制,攻击者搞个中间人攻击,你的数据就裸奔了。
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="false">
<domain includeSubdomains="true">api.yourdomain.com</domain>
<domain includeSubdomains="true">secure.yourdomain.com</domain>
</domain-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">cdn.yourdomain.com</domain>
</domain-config>
</network-security-config>
这里有个关键点:生产环境一定要把 cleartextTrafficPermitted 设为 false。我在项目中遇到过,测试环境为了省事开了明文,结果上线前忘了改回来,差点酿成大祸。
4.2 证书锁定:给 HTTPS 加把锁
HTTPS 本身依赖 CA 证书链。但问题是,如果 CA 被攻破或者被恶意签发证书,你的 HTTPS 就形同虚设。这时候就需要证书锁定(Certificate Pinning)。
说白了,证书锁定就是让你的 App 只信任特定的证书或公钥,而不是系统里所有的 CA。我建议用公钥锁定,因为证书有有效期,公钥相对稳定。
我的经验:锁定 2-3 个备用公钥。一个主用,一个备用,再加一个应急。我曾经因为只锁了一个,证书更换时 App 直接崩了,用户骂声一片。
// OkHttp 证书锁定示例
val certificatePinner = CertificatePinner.Builder()
.add("api.yourdomain.com",
"sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") // 主公钥
.add("api.yourdomain.com",
"sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=") // 备用的
.build()
val client = OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.build()
警告:证书锁定一旦配错,你的 App 就彻底连不上服务器了。所以一定要有降级机制,比如在 Debug 模式下关闭锁定,或者通过远程配置动态开关。
4.3 OkHttp 安全配置:细节决定成败
OkHttp 是 Android 最常用的网络库,但默认配置并不安全。我每次新建项目,第一件事就是调整 OkHttp 的配置。
为什么会这样?因为 OkHttp 默认信任所有系统 CA,而且没有超时限制。攻击者可以利用这点发起慢速攻击。
| 配置项 | 推荐值 | 说明 |
|---|---|---|
| connectTimeout | 10 秒 | 连接超时,太长容易被拖死 |
| readTimeout | 15 秒 | 读取超时,防止慢速攻击 |
| writeTimeout | 15 秒 | 写入超时,同上 |
| followRedirects | false | 关闭自动重定向,防止 SSRF |
| retryOnConnectionFailure | true | 重试机制,但要限制次数 |
val client = OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.followRedirects(false)
.retryOnConnectionFailure(true)
.addInterceptor(HttpLoggingInterceptor().apply {
level = if (BuildConfig.DEBUG)
HttpLoggingInterceptor.Level.BODY
else
HttpLoggingInterceptor.Level.NONE
})
.build()
记得把日志拦截器只在 Debug 模式开启。我曾经见过生产环境开着 BODY 级别日志,用户密码全打出来了,那画面太美不敢看。
4.4 WebView 安全:最容易忽视的漏洞
WebView 是 Android 安全的重灾区。我做过一次安全审计,发现 70% 的 App 在 WebView 配置上都有问题。
首先,一定要禁用 JavaScript 接口。除非你明确知道自己在做什么。我在项目中遇到过,某个第三方 SDK 偷偷开了 JS 接口,结果被 XSS 攻击,用户数据全被偷了。
// 安全的 WebView 配置
webView.settings.apply {
javaScriptEnabled = false // 默认关闭
allowFileAccess = false // 禁止文件访问
allowContentAccess = false // 禁止 content 协议
allowFileAccessFromFileURLs = false
allowUniversalAccessFromFileURLs = false
cacheMode = WebSettings.LOAD_NO_CACHE
}
// 如果需要 JS,一定要做白名单校验
webView.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
return !isUrlAllowed(url) // 只允许白名单 URL
}
}
避坑指南:我曾经接手过一个项目,WebView 开了 setAllowFileAccess(true),结果攻击者通过 file:// 协议读取了本地数据库。从那以后,我所有项目都默认关闭文件访问。
4.5 网络安全配置:全局把控
Android 9(API 28)之后,系统默认禁止明文流量。但很多老项目还在用旧配置。我建议统一使用 network_security_config.xml 来做全局管控。
你想想看,如果每个网络请求都单独配置,维护成本太高了。集中管理才是正道。
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="false">
<trust-anchors>
<certificates src="system" />
<!-- 如果需要,可以添加自定义 CA -->
<!-- <certificates src="user" /> -->
</trust-anchors>
</base-config>
<debug-overrides>
<trust-anchors>
<certificates src="user" />
</trust-anchors>
</debug-overrides>
</network-security-config>
我的习惯:Debug 模式允许用户证书,方便抓包调试。但 Release 模式只信任系统证书,杜绝中间人攻击。这个配置在 AndroidManifest 里引用一下就行:android:networkSecurityConfig="@xml/network_security_config"
最后说一句,网络安全不是配一次就完事的。要定期审查配置,更新证书,检查第三方库的安全版本。我每个月都会做一次安全配置的 review,这个习惯救了我好几次。