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

基于Vapor+Imperial的OAuth登录:如何触发客户端回调操作?

问题描述

我正在开发一款采用Vapor后端、Imperial实现OAuth认证的应用,需集成谷歌与Facebook登录功能。目前访问localhost:8080/google端点时,服务器会完成用户认证,随后执行服务器端回调获取token并跳转路径。客户端通过WKWebView嵌入该端点,认证流程正常,但因回调仅在服务器端执行,导致用户在WebView完成登录后,客户端无法执行接收token、关闭WebView、显示活动指示器等操作。请问是否可在服务器端回调完成后触发客户端请求?或是有其他方案,比如无需客户端主动请求的服务器推送方式?

后端认证代码

try group.oAuth(
        from: Google.self,
        authenticate: "google",
        authenticateCallback: nil,
        callback: "http://localhost:8080/google-complete",
        scope: ["profile", "email"],
        completion: processGoogleLogin
    )

processGoogleLogin函数

func processGoogleLogin(request: Request, token: String) throws -> EventLoopFuture<ResponseEncodable> {
    print("Google Token: ",token)
    return request.eventLoop.future(request.redirect(to: "/"))
    //here theoretically I want to do certain operations on the client side.
}

客户端嵌入WebView的代码

@objc private func googleLogin() {
    let newVC = UIViewController()

    let webConfiguration = WKWebViewConfiguration()
    let webView = WKWebView(frame: newVC.view.frame, configuration: webConfiguration)
    let userAgentValue = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/603.2.4 (KHTML, like Gecko) Version/10.1.1 Safari/603.2.4"
    webView.customUserAgent = userAgentValue
    
    let myURL = URL(string:"http://localhost:8080/google")
    let myRequest = URLRequest(url: myURL!)

    webView.uiDelegate = self
    newVC.view = webView
    webView.load(myRequest)
    self.present(newVC, animated: true)
}
解决方案

方案1:自定义URL Scheme触发客户端操作

这是移动端OAuth登录回调的标准方案,无需服务器推送,流程如下:

  1. 客户端配置自定义URL Scheme:在iOS项目的Info.plist中添加自定义Scheme(比如myapp://),用于接收服务器回调通知。
  2. 修改服务器回调逻辑:在processGoogleLogin中,重定向到自定义Scheme并携带参数(生产环境建议用后端会话ID替代直接传token):
func processGoogleLogin(request: Request, token: String) throws -> EventLoopFuture<ResponseEncodable> {
    print("Google Token: ",token)
    let callbackUrl = "myapp://login-complete?token=\(token)"
    return request.eventLoop.future(request.redirect(to: callbackUrl))
}
  1. 客户端处理URL回调:在AppDelegate或SceneDelegate中实现URL回调方法,执行后续操作:
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
    guard url.scheme == "myapp", url.host == "login-complete" else {
        return false
    }
    let components = URLComponents(url: url, resolvingAgainstBaseURL: false)
    let token = components?.queryItems?.first(where: { $0.name == "token" })?.value
    
    if let presentedVC = window?.rootViewController?.presentedViewController {
        presentedVC.dismiss(animated: true) {
            print("Received token: \(token ?? "")")
            // 这里可添加活动指示器、保存token等操作
        }
    }
    return true
}
  1. WKWebView拦截跳转:在WKNavigationDelegate中拦截自定义Scheme跳转,避免WebView报错:
extension YourViewController: WKNavigationDelegate {
    func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
        guard let url = navigationAction.request.url else {
            decisionHandler(.allow)
            return
        }
        if url.scheme == "myapp" {
            UIApplication.shared.open(url, options: [:], completionHandler: nil)
            decisionHandler(.cancel)
        } else {
            decisionHandler(.allow)
        }
    }
}

方案2:WKWebView与JavaScript交互触发操作

若不想用自定义Scheme,可让服务器返回带JS的HTML页面,调用客户端原生方法:

  1. 服务器返回JS触发页面:修改processGoogleLogin返回包含JS的HTML:
func processGoogleLogin(request: Request, token: String) throws -> EventLoopFuture<ResponseEncodable> {
    print("Google Token: ",token)
    let html = """
    <html>
        <body>
            <script>
                window.webkit.messageHandlers.loginComplete.postMessage({token: "\(token)"});
            </script>
        </body>
    </html>
    """
    return request.eventLoop.future(Response(body: .init(string: html)))
}
  1. 客户端配置WKWebView消息处理器:初始化WebView时添加消息处理器:
@objc private func googleLogin() {
    let newVC = UIViewController()

    let webConfiguration = WKWebViewConfiguration()
    webConfiguration.userContentController.add(self, name: "loginComplete")
    
    let webView = WKWebView(frame: newVC.view.frame, configuration: webConfiguration)
    let userAgentValue = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/603.2.4 (KHTML, like Gecko) Version/10.1.1 Safari/603.2.4"
    webView.customUserAgent = userAgentValue
    
    let myURL = URL(string:"http://localhost:8080/google")
    let myRequest = URLRequest(url: myURL!)

    webView.uiDelegate = self
    webView.navigationDelegate = self
    newVC.view = webView
    webView.load(myRequest)
    self.present(newVC, animated: true)
}
  1. 实现WKScriptMessageHandler协议:处理JS传递的消息:
extension YourViewController: WKScriptMessageHandler {
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        guard message.name == "loginComplete", let body = message.body as? [String: String], let token = body["token"] else {
            return
        }
        self.dismiss(animated: true) {
            print("Received token via JS: \(token)")
            // 执行关闭WebView、显示活动指示器等操作
        }
    }
}

关于服务器推送的说明

移动端服务器主动推送依赖APNs(iOS)或FCM(Android),但这种方式不适合OAuth登录的即时回调场景——登录流程是同步操作,推送存在延迟或送达不稳定问题,因此上述两种方案更适配需求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 12:15:36