WebView 通信与安全边界
🌐 WebView 与原生之间的双向通信是 Hybrid 应用的核心。清晰安全边界是防止总击的关键。
JS Bridge 模式
Native 向 Web 发消息
// iOS (WKWebView)
webView.evaluateJavaScript("""
window.dispatchEvent(new CustomEvent('native-message', {
detail: { type: 'userInfo', data: \(jsonString) }
}))
""")
// Android
webView.evaluateJavascript("""
window.dispatchEvent(new CustomEvent('native-message', {
detail: { type: 'userInfo', data: $jsonString }
}))
""".trimIndent(), null)
Web 向 Native 发消息
// 方式一:URL Scheme 拦截
window.location.href = 'myapp://action?param=value'
// 方式二:JavaScript 接口注入(Android)
window.AndroidBridge.postMessage(JSON.stringify(payload))
// 方式三:MessageHandler(iOS WKWebView)
window.webkit.messageHandlers.bridge.postMessage(payload)
成熟的 JSBridge 实现
// Web 侧 SDK
const callNative = (method, params) => {
return new Promise((resolve, reject) => {
const callId = `cb_${Date.now()}`
window.__callbacks = window.__callbacks || {}
window.__callbacks[callId] = { resolve, reject }
const message = JSON.stringify({ method, params, callId })
if (window.AndroidBridge) {
window.AndroidBridge.postMessage(message)
} else if (window.webkit?.messageHandlers?.bridge) {
window.webkit.messageHandlers.bridge.postMessage(message)
}
})
}
// Native 回调时调用
window.__bridgeCallback = (callId, result, error) => {
const cb = window.__callbacks[callId]
if (error) cb.reject(error)
else cb.resolve(result)
delete window.__callbacks[callId]
}
安全边界
XSS 防止
// Android - 限制 JS 执行
webSettings.javaScriptEnabled = true // 开启 JS
webSettings.allowFileAccess = false // 禁止本地文件访问
webSettings.allowContentAccess = false
// 白名单展示
// 只允许 HTTPS 内容
webSettings.mixedContentMode = WebSettings.MIXED_CONTENT_NEVER_ALLOW
注入辞尚
// iOS - 加载自定义后打开外部页面
let config = WKWebViewConfiguration()
config.preferences.javaScriptEnabled = true
// 内容安全策略 - 限制外部资源
let csp = "default-src 'self'; script-src 'self'"
config.userContentController.addContentWorld()
URL Scheme 验证
// Android - 验证 URL Scheme 源
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
val uri = request.url
if (uri.scheme == "myapp") {
// 验证 referrer 与预期 domain 一致
if (request.requestHeaders["Referer"]?.startsWith("https://trusted.com") == true) {
handleBridgeCall(uri)
}
return true
}
return false
}
常见误区
- Android 开启
addJavascriptInterface但没有限制调用方,任意 JS 都可调用 Native - 不验证 postMessage 的 origin,导致氐染页面可以调用权限接口
- HTTP 内容在 WebView 被内嵌,允许 mixed content 导致中间人攻击