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

Swift中如何等待OAuth回调完成后再处理回调URL?

Swift OAuth授权流程时序问题:Token未完成存储就触发页面刷新

触发runOauth方法后,AppDelegate的application(_:open:options:)会先于callBackOAuth回调执行,导致授权完成后viewDidAppear重新调用时,keychain中的Token尚未被设置。


相关代码实现

1. OAuth授权触发与回调方法

func runOauth(){
    let appDelegate = UIApplication.shared.delegate as! AppDelegate
    appDelegate.oauth2!.afterAuthorizeOrFail = self.callBackOAuth

    var url:URL?
    do{
        url = try appDelegate.oauth2!.authorizeURL(withRedirect:"kronos://oauth/callback", scope: "auth",params: ["tg":"addon/kronos/main","idx":"login.OAuth","formId":"iOS"])
        do{
            let authorizer = appDelegate.oauth2!.authorizer as! OAuth2Authorizer
            safariVC = try authorizer.authorizeSafariEmbedded(from: self,at: url!)
            
        }
      /*catch error*/
    }
}


func callBackOAuth(authParameters:OAuth2JSON!, error: OAuth2Error!){
        let appDelegate = UIApplication.shared.delegate as! AppDelegate
        if (error == nil && appDelegate.oauth2!.accessToken != nil){//OAuth授权成功
            self.keychain!.set(appDelegate.oauth2!.accessToken!,forKey:"Token")
            appDelegate.reloadView()
        }else {/*错误处理逻辑*/}
    }

2. AppDelegate的URL回调处理

func application(_ app: UIApplication,
                  open url: URL,
                  options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
        let components = URLComponents(url: url, resolvingAgainstBaseURL: false)
        let site=components?.host
        if site == "oauth"{//OAuth流程结束
            if components?.path == "/callback" {
                let viewController = self.window?.rootViewController as! ViewController
                self.oauth2!.handleRedirectURL(url)
                viewController.hideSafariView()
            }
        }
        return true
    }

3. ViewController的viewDidAppear方法

override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        let appDelegate = UIApplication.shared.delegate as! AppDelegate
        //初始化OAuth2配置参数
        appDelegate.oauth2 = OAuth2CodeGrant(settings: OAuthParams  )
        appDelegate.oauth2!.authConfig.authorizeContext = KronosWebsite?.window
        appDelegate.oauth2!.useKeychain = false
        appDelegate.oauth2!.authConfig.authorizeEmbeddedAutoDismiss = true
        appDelegate.oauth2!.logger = OAuth2DebugLogger(.debug)
        appDelegate.oauth2!.afterAuthorizeOrFail = self.callBackOAuth

        let token=self.keychain!.get("Token")        
        if(token == nil){//未找到Token,启动OAuth流程
            runOauth()
        } 
}

4. 尝试过的DispatchGroup方案(未成功)

// runOauth中添加
groupOauth.enter()

// callBackOAuth中添加
groupOauth.leave()

// AppDelegate的application方法中修改为
viewController.groupOauth.notify(queue: DispatchQueue.main) {
    self.oauth2!.handleRedirectURL(url)
    viewController.hideSafariView()
}

解决方案

方案1:调整Safari关闭与Token存储的执行顺序

问题核心是AppDelegate中先执行了hideSafariView和页面刷新,而callBackOAuth(Token存储逻辑)是handleRedirectURL内部异步触发的,导致时序颠倒。

修改步骤:

  1. 移除AppDelegate中的Safari关闭调用,仅保留URL处理:
func application(_ app: UIApplication,
                  open url: URL,
                  options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
        let components = URLComponents(url: url, resolvingAgainstBaseURL: false)
        let site=components?.host
        if site == "oauth"{
            if components?.path == "/callback" {
                self.oauth2!.handleRedirectURL(url)
                // 移除 viewController.hideSafariView()
            }
        }
        return true
    }
  1. 在回调方法中统一处理Token存储、Safari关闭和页面刷新:
func callBackOAuth(authParameters:OAuth2JSON!, error: OAuth2Error!){
        let appDelegate = UIApplication.shared.delegate as! AppDelegate
        if (error == nil && appDelegate.oauth2!.accessToken != nil){
            // 1. 存储Token
            self.keychain!.set(appDelegate.oauth2!.accessToken!,forKey:"Token")
            // 2. 关闭Safari视图
            self.hideSafariView()
            // 3. 触发页面刷新
            appDelegate.reloadView()
        }else {
            // 授权失败时也关闭Safari
            self.hideSafariView()
            /*错误处理逻辑*/
        }
    }

方案2:避免重复初始化OAuth实例

当前viewDidAppear每次都会重新创建oauth2实例,可能导致回调绑定混乱。将实例初始化移到AppDelegate启动方法中,仅初始化一次:

// AppDelegate.swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // 初始化OAuth2实例
    oauth2 = OAuth2CodeGrant(settings: OAuthParams)
    oauth2!.authConfig.authorizeContext = KronosWebsite?.window
    oauth2!.useKeychain = false
    oauth2!.authConfig.authorizeEmbeddedAutoDismiss = true
    oauth2!.logger = OAuth2DebugLogger(.debug)
    return true
}

修改viewDidAppear,仅绑定回调和检查Token:

override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        let appDelegate = UIApplication.shared.delegate as! AppDelegate
        // 仅绑定回调,不再重复初始化实例
        appDelegate.oauth2!.afterAuthorizeOrFail = self.callBackOAuth

        let token=self.keychain!.get("Token")        
        if(token == nil){
            runOauth()
        } 
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 10:11:07