You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在WKWebView或iOS端PWA中支持WebAuthn及跨组件数据共享?

解决方案:WKWebView中WebAuthn失效及SFSafariViewController与WKWebView数据共享

一、WKWebView不支持WebAuthn的可行方案

1. 升级iOS/iPadOS版本到16.4+

iOS 16.4和iPadOS 16.4正式为WKWebView添加了WebAuthn支持,如果你的App目标用户系统版本可以覆盖到这个范围,直接升级系统后就能让原PWA的WebAuthn功能正常工作,无需额外修改。

2. 改用SFSafariViewController承载认证流程

SFSafariViewController完全支持WebAuthn,这是你已经尝试的方案,重点解决后续的数据传递问题,详见第二部分。

3. 原生封装WebAuthn能力,通过JS桥与WKWebView交互

如果必须用WKWebView,可以用苹果原生的LAContext(LocalAuthentication)实现生物识别,再通过WKWebView的WKScriptMessageHandler搭建JS与原生的通信桥:

  • 原生端实现生物识别逻辑,生成或验证WebAuthn所需的凭证
  • 在WKWebView中注册JS消息处理器,网页端触发生物识别请求
  • 原生完成认证后,将结果通过evaluateJavaScript(_:)注入到网页中

二、SFSafariViewController与WKWebView的数据共享方法

以下是几种可靠的实现方式,按安全性和易用性排序:

1. 自定义URL Scheme(最直接的跳转回调)

步骤:

  • 在Xcode项目的「Info」→「URL Types」中添加自定义Scheme(比如yourapp-auth)
  • 在认证页面完成后,通过JS跳转到该Scheme并携带参数:
    // 认证成功后执行
    const authToken = "获取到的认证凭证";
    window.location.href = `yourapp-auth://complete?token=${encodeURIComponent(authToken)}`;
    
  • 在App的SceneDelegate(或AppDelegate)中拦截URL,解析参数并传递给WKWebView:
    func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
        guard let url = URLContexts.first?.url, url.scheme == "yourapp-auth" else { return }
        
        // 解析查询参数
        let components = URLComponents(url: url, resolvingAgainstBaseURL: false)
        let token = components?.queryItems?.first(where: { $0.name == "token" })?.value ?? ""
        
        // 获取当前WKWebView实例,注入数据到网页
        if let webView = (scene as? UIWindowScene)?.windows.first?.rootViewController?.view as? WKWebView {
            webView.evaluateJavaScript("window.handleAuthSuccess('\(token)')", completionHandler: { error in
                if let error = error {
                    print("注入JS失败:\(error)")
                }
            })
        }
    }
    
  • 网页端提前定义handleAuthSuccess函数接收凭证:
    function handleAuthSuccess(token) {
        // 用token完成登录逻辑
        console.log("收到认证凭证:", token);
    }
    

2. 共享Keychain存储(适合敏感数据)

因为SFSafariViewController和WKWebView同属一个App进程,可通过Keychain安全存储认证信息:

  • 在原生端封装Keychain读写方法,比如:
    class KeychainHelper {
        static func save(key: String, value: String) {
            let data = value.data(using: .utf8)!
            let query: [String: Any] = [
                kSecClass as String: kSecClassGenericPassword,
                kSecAttrAccount as String: key,
                kSecValueData as String: data
            ]
            SecItemDelete(query as CFDictionary)
            SecItemAdd(query as CFDictionary, nil)
        }
        
        static func load(key: String) -> String? {
            let query: [String: Any] = [
                kSecClass as String: kSecClassGenericPassword,
                kSecAttrAccount as String: key,
                kSecReturnData as String: kCFBooleanTrue!,
                kSecMatchLimit as String: kSecMatchLimitOne
            ]
            var data: AnyObject?
            let status = SecItemCopyMatching(query as CFDictionary, &data)
            guard status == errSecSuccess, let data = data as? Data else { return nil }
            return String(data: data, encoding: .utf8)
        }
    }
    
  • 在SFSafariViewController加载的页面中,通过JS桥调用原生保存凭证:
    window.webkit.messageHandlers.saveAuthToken.postMessage({ token: "敏感凭证" });
    
  • 原生处理消息并保存到Keychain:
    class AuthMessageHandler: NSObject, WKScriptMessageHandler {
        func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
            if message.name == "saveAuthToken", let body = message.body as? [String: String], let token = body["token"] {
                KeychainHelper.save(key: "auth_token", value: token)
            }
        }
    }
    
  • 返回WKWebView后,网页端调用原生读取Keychain数据:
    window.webkit.messageHandlers.getAuthToken.postMessage(null);
    
  • 原生读取后注入网页:
    // 在getAuthToken的消息处理中
    if let token = KeychainHelper.load(key: "auth_token") {
        webView.evaluateJavaScript("window.receiveAuthToken('\(token)')", completionHandler: nil)
    }
    

3. 共享UserDefaults(适合非敏感数据)

原理和Keychain类似,但UserDefaults是明文存储,只适合非敏感的临时数据:

  • 认证成功后,通过JS桥将数据存入UserDefaults:
    UserDefaults.standard.set(token, forKey: "temp_auth_token")
    
  • 返回WKWebView后,网页端通过JS桥读取或由原生主动注入数据。

4. 利用SFSafariViewController代理回调

实现SFSafariViewControllerDelegate的safariViewControllerDidFinish(_:)方法,在用户点击「完成」按钮时触发数据同步:

func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
    // 读取提前存储的认证数据(比如Keychain/UserDefaults)
    if let token = KeychainHelper.load(key: "auth_token") {
        // 传递给WKWebView
        webView.evaluateJavaScript("window.handleAuthSuccess('\(token)')", completionHandler: nil)
        // 用完清空临时存储
        KeychainHelper.save(key: "auth_token", value: "")
    }
}

内容的提问来源于stack exchange,提问作者Abhi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 07:20:33