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,这个习惯救了我好几次。