You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

iOS 11下第三方App OAuth登录后官方App自动登录问题咨询

这确实是iOS 11之后OAuth跨应用免登的一个常见痛点——之前依赖SFSafariViewController会话共享的路子被封了,给你几个经过实际项目验证的可行方案,你可以根据自己的业务场景选择:

方案1:通过App Groups共享Keychain存储OAuth令牌

这是最直接的方案,适合同一开发者账号下的第三方应用和官方应用,利用iOS的Keychain共享机制,把用户的OAuth刷新令牌(refresh token)存在共享容器里,官方APP启动时直接读取并完成自动登录。

实现步骤:

  1. 配置App Groups:在Apple开发者后台给两个应用开启同一个App Groups(比如group.com.yourapp.shared),并在Xcode的项目配置中勾选对应的App Groups权限。
  2. 第三方APP存储令牌:用户登录成功后,将refresh token存入共享Keychain:
// Objective-C示例
NSDictionary *saveQuery = @{
    (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
    (__bridge id)kSecAttrAccount: @"user_oauth_refresh_token",
    (__bridge id)kSecAttrAccessGroup: @"group.com.yourapp.shared", // 对应App Groups标识
    (__bridge id)kSecValueData: [refreshToken dataUsingEncoding:NSUTF8StringEncoding],
    (__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleWhenUnlocked // 按需选择访问权限
};

OSStatus status = SecItemAdd((__bridge CFDictionaryRef)saveQuery, NULL);
if (status == errSecDuplicateItem) {
    // 如果已存在,更新令牌
    NSDictionary *updateQuery = @{
        (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
        (__bridge id)kSecAttrAccount: @"user_oauth_refresh_token",
        (__bridge id)kSecAttrAccessGroup: @"group.com.yourapp.shared"
    };
    NSDictionary *updateValues = @{(__bridge id)kSecValueData: [refreshToken dataUsingEncoding:NSUTF8StringEncoding]};
    SecItemUpdate((__bridge CFDictionaryRef)updateQuery, (__bridge CFDictionaryRef)updateValues);
}
  1. 官方APP读取并自动登录:首次启动时,从共享Keychain读取refresh token,调用后端接口换取新的access token,完成自动登录:
// Swift示例
let query: [CFString: Any] = [
    kSecClass: kSecClassGenericPassword,
    kSecAttrAccount: "user_oauth_refresh_token",
    kSecAttrAccessGroup: "group.com.yourapp.shared" as CFString,
    kSecReturnData: kCFBooleanTrue,
    kSecMatchLimit: kSecMatchLimitOne
]

var data: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &data)
if status == errSecSuccess, let tokenData = data as? Data, let refreshToken = String(data: tokenData, encoding: .utf8) {
    // 调用后端接口,用refreshToken获取access token,完成自动登录
    performAutoLogin(with: refreshToken)
}

注意事项:

  • 确保两个应用的Bundle ID都关联了同一个App Groups,且开发者账号有权限配置。
  • 选择合适的kSecAttrAccessible值,平衡安全性和可用性(比如kSecAttrAccessibleWhenUnlocked适合大多数场景)。

方案2:通过Universal Links传递一次性登录凭证

如果第三方应用和官方应用不属于同一开发者账号,或者你不想依赖Keychain共享,可以用Universal Links配合后端生成一次性凭证来实现免登。

实现步骤:

  1. 配置Universal Links:给官方APP配置Universal Links(比如https://yourapp.com/autologin),确保后端的apple-app-site-association文件正确配置。
  2. 第三方APP触发凭证生成:用户登录成功后,请求后端生成一个一次性、短期有效的登录凭证(比如JWT),然后通过Universal Links唤起官方APP并携带凭证:
// Swift示例
func triggerAutoLoginForOfficialApp() {
    // 先请求后端生成一次性凭证
    APIClient.generateOneTimeLoginToken { result in
        switch result {
        case .success(let oneTimeToken):
            guard let url = URL(string: "https://yourapp.com/autologin?token=\(oneTimeToken)") else { return }
            UIApplication.shared.open(url, options: [:], completionHandler: nil)
        case .failure(let error):
            // 处理错误
            print(error.localizedDescription)
        }
    }
}
  1. 官方APP接收并验证凭证:在APP delegate的application(_:continue:restorationHandler:)方法(或SwiftUI的onOpenURL回调)中接收Universal Links,解析凭证参数,向后端验证凭证有效性,验证通过后完成自动登录:
// SwiftUI示例
.onOpenURL { url in
    guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true),
          let token = components.queryItems?.first(where: { $0.name == "token" })?.value else {
        return
    }
    // 向后端验证token有效性
    APIClient.validateOneTimeToken(token) { result in
        switch result {
        case .success(let userSession):
            // 保存用户会话,完成自动登录
            UserSessionManager.shared.saveSession(userSession)
        case .failure(let error):
            // 验证失败,弹出登录界面
            showLoginSheet = true
        }
    }
}

注意事项:

  • 一次性凭证必须设置较短的过期时间(比如5分钟),且只能使用一次,防止被滥用。
  • 传递凭证时建议对参数进行加密,或者用HTTPS确保传输安全。

方案3:利用Shared Web Credentials共享登录凭据

如果你的网站本身支持Shared Web Credentials,这个方案可以让应用和Safari共享登录凭据,即使SFSafariViewController的会话共享被禁,依然能实现跨应用免登。

实现步骤:

  1. 配置网站的Shared Web Credentials:在网站的根目录或.well-known目录下放置apple-app-site-association文件,添加webcredentials字段,包含两个应用的Team ID和Bundle ID:
{
  "webcredentials": {
    "apps": ["YOUR_TEAM_ID.com.yourapp.thirdparty", "YOUR_TEAM_ID.com.yourapp.official"]
  }
}
  1. 应用配置Entitlements:在第三方应用和官方应用的Entitlements中开启com.apple.developer.shared-web-credentials权限。
  2. 第三方APP保存凭据:用户通过SFSafariViewController登录成功后,将用户的登录凭据(比如refresh token)存入Shared Web Credentials:
// Swift示例
func saveCredentialToSharedWebCredentials(userId: String, refreshToken: String) {
    let protectionSpace = URLProtectionSpace(
        host: "yourapp.com",
        port: 443,
        protocol: "https",
        realm: nil,
        authenticationMethod: NSURLAuthenticationMethodHTTPBasic
    )
    let credential = URLCredential(
        user: userId,
        password: refreshToken,
        persistence: .permanent
    )
    URLCredentialStorage.shared.set(credential, for: protectionSpace)
}
  1. 官方APP读取凭据:首次启动时,从Shared Web Credentials中读取凭据,完成自动登录:
// Swift示例
func loadCredentialForAutoLogin() {
    let protectionSpace = URLProtectionSpace(
        host: "yourapp.com",
        port: 443,
        protocol: "https",
        realm: nil,
        authenticationMethod: NSURLAuthenticationMethodHTTPBasic
    )
    if let credentials = URLCredentialStorage.shared.credentials(for: protectionSpace)?.values {
        for credential in credentials {
            guard let userId = credential.user, let refreshToken = credential.password else { continue }
            // 使用refreshToken完成自动登录
            performAutoLogin(userId: userId, refreshToken: refreshToken)
            break
        }
    }
}

注意事项:

  • 用户需要允许应用访问共享凭据(通常在首次保存时会弹出授权提示,用户同意后才能共享)。
  • 确保网站的apple-app-site-association文件能被苹果的爬虫正确抓取(HTTPS必须有效,不能有重定向)。

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

火山引擎 最新活动