网络安全通信:HTTPS配置、SSL Pinning实现、证书校验、WebView安全配置

大家好,我是你们的老朋友。今天我们来聊聊网络安全通信这个话题。说实话,这是Android安全开发里最容易被忽视,但又最容易出大问题的一块。

我记得刚入行那会儿,有个同事把HTTPS配置搞错了,结果用户数据在传输过程中被中间人截获。嗯,那场面,真是惨不忍睹。从那以后,我对网络通信这块就格外上心。

今天我会从四个维度来拆解:HTTPS怎么配、SSL Pinning怎么玩、证书校验有哪些坑、WebView怎么安全配置。每个点我都会结合自己的实战经验来讲。

一、HTTPS配置:别以为配了就安全了

很多人觉得,只要用了HTTPS就万事大吉。其实不然。我见过太多项目,HTTPS配了,但用的是自签名证书,或者干脆关闭了证书校验。

先看一个标准的HTTPS配置示例:

// 在AndroidManifest.xml中配置网络安全策略
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config cleartextTrafficPermitted="false">
        <domain includeSubdomains="true">api.yourdomain.com</domain>
        <trust-anchors>
            <certificates src="@raw/ca_cert"/>
        </trust-anchors>
    </domain-config>
</network-security-config>

这里有几个关键点:

  • cleartextTrafficPermitted="false":强制使用HTTPS,不允许明文传输
  • includeSubdomains="true":子域名也受保护
  • trust-anchors:指定信任的CA证书
⚠️ 注意:千万不要在生产环境使用 cleartextTrafficPermitted="true"。我曾经在一个金融App里看到这个配置,吓得我赶紧联系了对方的安全团队。

二、SSL Pinning:给证书加把锁

SSL Pinning,说白了就是让App只信任特定的证书或公钥。为什么要这么做?因为默认的证书校验机制存在一个漏洞:只要CA签发的证书都能通过验证。如果CA被攻破,或者有人伪造了CA证书,你的通信就危险了。

我个人习惯用公钥固定(Public Key Pinning),因为证书会过期,但公钥不会。来看代码:

public class PinningTrustManager implements X509TrustManager {
    private static final String PINNED_PUBLIC_KEY = "SHA256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
    
    @Override
    public void checkServerTrusted(X509Certificate[] chain, String authType) 
            throws CertificateException {
        if (chain == null || chain.length == 0) {
            throw new CertificateException("证书链为空");
        }
        
        // 获取服务器证书的公钥
        PublicKey serverKey = chain[0].getPublicKey();
        String serverKeyHash = hashPublicKey(serverKey);
        
        // 与固定的公钥比较
        if (!PINNED_PUBLIC_KEY.equals(serverKeyHash)) {
            throw new CertificateException("公钥不匹配,可能存在中间人攻击");
        }
    }
    
    private String hashPublicKey(PublicKey key) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            byte[] digest = md.digest(key.getEncoded());
            return Base64.encodeToString(digest, Base64.NO_WRAP);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("SHA-256算法不可用", e);
        }
    }
}
💡 避坑指南:我曾经遇到过一个问题:证书更新后,公钥变了,导致所有用户都无法连接。所以建议至少固定两个公钥——一个当前使用的,一个备份的。这样切换证书时不会断服务。

三、证书校验:别跳过,也别乱写

证书校验是HTTPS安全的核心。但很多开发者为了省事,直接写了个空实现。你想想看,这跟裸奔有什么区别?

正确的做法是:

  1. 校验证书链:从服务器证书到根证书,每一级都要验证
  2. 校验主机名:确保证书的CN或SAN与请求的域名匹配
  3. 校验有效期:证书是否在有效期内
  4. 校验吊销状态:通过CRL或OCSP检查证书是否被吊销

来看一个完整的校验示例:

public class CustomHostnameVerifier implements HostnameVerifier {
    @Override
    public boolean verify(String hostname, SSLSession session) {
        try {
            // 获取服务器证书
            Certificate[] certs = session.getPeerCertificates();
            if (certs == null || certs.length == 0) {
                return false;
            }
            
            // 检查主机名是否匹配
            X509Certificate cert = (X509Certificate) certs[0];
            String cn = getCN(cert);
            if (!hostname.equals(cn)) {
                // 检查SAN扩展
                Collection<List<?>> san = cert.getSubjectAlternativeNames();
                if (san != null) {
                    for (List<?> entry : san) {
                        if (entry.get(1).toString().equals(hostname)) {
                            return true;
                        }
                    }
                }
                return false;
            }
            return true;
        } catch (SSLPeerUnverifiedException e) {
            return false;
        }
    }
}
🔑 核心要点:证书校验不是可选项,是必选项。不要为了调试方便就跳过校验,调试完记得改回来。我见过太多因为调试代码没删干净导致的安全事故。

四、WebView安全配置:最容易忽视的漏洞

WebView的安全配置,说实话,是Android安全里最容易被忽视的一块。很多人觉得WebView就是个浏览器,能有什么问题?问题大了去了。

先看一个不安全的配置:

// ❌ 不安全的配置
WebView webView = findViewById(R.id.webview);
webView.getSettings().setJavaScriptEnabled(true);
webView.getSettings().setAllowFileAccess(true);
webView.getSettings().setAllowContentAccess(true);
webView.setWebViewClient(new WebViewClient() {
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        return false; // 允许所有URL
    }
});

这个配置的问题在于:

  • JavaScript启用:可能被XSS攻击利用
  • 文件访问开启:攻击者可以读取本地文件
  • 内容访问开启:可能被用来访问Content Provider
  • URL不校验:可能跳转到恶意网站

正确的配置应该是这样的:

// ✅ 安全的配置
WebView webView = findViewById(R.id.webview);
WebSettings settings = webView.getSettings();

// 按需启用JavaScript
settings.setJavaScriptEnabled(false); // 如果不需要,就关掉

// 关闭不必要的访问
settings.setAllowFileAccess(false);
settings.setAllowContentAccess(false);
settings.setAllowFileAccessFromFileURLs(false);
settings.setAllowUniversalAccessFromFileURLs(false);

// 设置安全的WebViewClient
webView.setWebViewClient(new WebViewClient() {
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
        String url = request.getUrl().toString();
        // 只允许加载HTTPS链接
        if (url.startsWith("https://")) {
            return false;
        }
        return true;
    }
    
    @Override
    public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
        // 不要调用handler.proceed(),应该拒绝
        handler.cancel();
    }
});
⚠️ 重要提醒:千万不要在WebView里调用 addJavascriptInterface() 暴露Java对象给JavaScript。除非你非常清楚自己在做什么,并且做了严格的权限控制。我曾经因为这个接口被滥用,导致用户数据泄露。

五、知识体系总览

说了这么多,我们来梳理一下整个网络安全通信的知识体系。下面这张图可以帮你快速理解各个模块之间的关系:

网络安全通信知识体系 网络安全通信 HTTPS配置 SSL Pinning 证书校验 WebView安全 强制HTTPS 证书信任锚 域名白名单 公钥固定 证书固定 备份公钥 证书链校验 主机名校验 吊销状态检查 禁用文件访问 URL白名单 SSL错误处理 四者缺一不可,共同构建安全通信防线

从这张图可以看出,网络安全通信不是单一的技术点,而是一个完整的体系。HTTPS配置是基础,SSL Pinning是加固,证书校验是保障,WebView安全是补充。四者缺一不可。

💡 我的建议:在实际项目中,先把HTTPS配置好,然后加上SSL Pinning,再完善证书校验逻辑,最后检查WebView的安全配置。按这个顺序来,不容易遗漏。

好了,今天就聊到这里。网络安全通信这块内容不少,但每个点都很关键。希望你能把这些知识用到实际项目中去,别让用户数据在传输过程中出问题。


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