从零为 iOS App 接入 Apple、Google、X、LINE 登录

9 阅读11分钟

给新 App 接入 Apple、Google、X 和 LINE 登录,iOS 端要面对四套平台配置和几种不同的回调。它们最终汇入同一条链路:取得平台凭证、交给业务服务端核验、建立 App 自己的会话。先看清这条链路,再逐个处理配置和 SDK,联调时就能知道问题出在哪个环节。

文中的实现对照来自两套匿名 iOS 工程的当前代码。涉及服务端的结论只覆盖客户端可见的请求和响应。

一次登录经过哪些环节

用户点击按钮后,iOS 通过系统 API、SDK 或网页认证会话取得平台凭证。业务服务端核验凭证,查找或创建内部用户,并签发产品会话。客户端保存会话,用它访问后续业务接口。

sequenceDiagram
    actor U as 用户
    participant I as iOS App
    participant P as Apple、Google、X、LINE
    participant B as 业务服务端
    U->>I: 点击登录方式
    I->>P: 系统 API、SDK 或浏览器发起授权
    P-->>I: ID Token 或授权码等凭证
    I->>B: 发送凭证及平台类型
    B->>P: 验签或调用平台接口核验身份
    B->>B: 查找或创建内部用户,签发产品会话
    B-->>I: 内部用户信息和产品 Token
    I->>I: 保存会话,切换已登录状态
    I->>B: 后续业务请求携带产品 Token

因此需要分开看两个结果:平台授权有没有成功,以及业务服务端有没有建立会话。客户端传来的平台用户 ID、邮箱或头像都可以伪造,服务端应从经过验证的凭证中取得稳定身份。Google 和 LINE 的官方文档都明确提醒了这一点。Google 服务端认证、LINE iOS 集成

核查的两套实现均由自有账号接口返回产品 Token,客户端保存账号后在后续请求中携带该 Token。一套工程在启动时要求登录;另一套允许游客进入主界面,只在受保护操作前弹出登录。门禁不同,凭证核验和业务会话的边界相同。服务端是否实际验签、怎样处理重放、如何保存密钥,单靠 iOS 代码无法确认。

接入前,把账号契约写清楚

建议先约定每个渠道向服务端提交哪种凭证,以及服务端按什么受众和规则校验。登录响应至少要让客户端明确知道内部用户是谁、会话何时失效、怎样刷新,以及首次注册、账号冲突或注销中的下一步动作。用户取消授权时业务接口不应被调用;服务端登录失败也不能让客户端进入已登录状态。

账号映射宜使用“平台 + 经过验证的稳定用户标识”。邮箱、昵称和头像可能变化,不适合作唯一键。同一人使用不同平台登录后是否合并账号、游客数据是否升级、首次登录是否补资料,都需要产品和服务端先定规则。iOS 只消费明确的响应,不根据缺失的资料猜测账号状态。

配置也有归属。App ID、Bundle ID 和回调地址由 iOS 与平台管理员共同核对;公开的 Client ID、Channel ID 可放在客户端环境配置中;Client Secret 和 Apple 私钥应由服务端或密钥管理系统保管。测试与正式环境分别登记应用、回调和受众,任何凭证值都不进入日志或公开文档。

下面的 Swift 片段仅保留授权到业务登录的主链路。complete(...) 表示把第三方凭证送到业务服务端、取得并保存业务会话;.apple 等凭证类型、PKCE 等辅助方法是示意名称。错误、取消和回调校验仍须按正文实现,示例不包含真实配置值。

Apple:系统授权与身份令牌

原生 iOS App 先在 Apple Developer 的 Identifiers 中为 App ID 开启 Sign in with Apple,再在 Xcode target 添加对应 capability,并核对签名与 entitlements。授权页由系统的 AuthenticationServices 展示,无须引入额外的 Apple 登录 SDK。若同一账号还要支持网页或其他平台,再按方案配置 Services ID;服务端如需交换授权码、维护刷新令牌或撤销授权,相关密钥材料留在服务端。Apple 能力配置、授权流程

iOS 请求必要的姓名、邮箱范围后,系统回调可提供 ID Token、一次性授权码、用户标识和可选资料。业务服务端收到约定的凭证后,应校验签名、签发方、受众、有效期,以及协议采用的 nonce 或授权码策略,再建立内部账号。Apple 服务端验证

Apple 案例代码

import AuthenticationServices

let request = ASAuthorizationAppleIDProvider().createRequest()
request.requestedScopes = [.fullName, .email]
let controller = ASAuthorizationController(authorizationRequests: [request])
controller.delegate = self
controller.presentationContextProvider = self
controller.performRequests()

// ASAuthorizationControllerDelegate 的成功回调:
guard let credential = authorization.credential as? ASAuthorizationAppleIDCredential,
      let data = credential.identityToken,
      let idToken = String(data: data, encoding: .utf8) else { return }
complete(.apple(idToken: idToken))

姓名通常只在首次授权返回。用户取消时,delegate 的错误回调应结束当前尝试,保留原有账号态。两套实现的提交内容并不完全一样:一套主要传 ID Token,另一套还传授权码和可选资料;这是各自的服务端契约,接入新 App 时要先选定。Apple 资料返回说明

Google:SDK 回调后交给服务端核验

先在 Google Cloud 项目配置 OAuth 同意信息,创建与 iOS 应用标识对应的 iOS OAuth Client。iOS Client ID 从控制台 Credentials 页面取得;Xcode 中还需登记其反向 URL scheme,并把登录回跳的 URL 交给 Google Sign-In SDK。若服务端受众或授权码方案需要单独的 Web/Server Client,应一并创建,由服务端保管需要保密的配置。Google iOS 配置、URL 回调

以 ID Token 为例,SDK 授权成功后,iOS 将它交给业务服务端。服务端验证签名、iss、aud 和有效期,再使用令牌中的稳定 subject 映射账号并返回产品 Token。Google 后端认证

Google 案例代码

import GoogleSignIn

GIDSignIn.sharedInstance.configuration = GIDConfiguration(clientID: clientID)
GIDSignIn.sharedInstance.signIn(withPresenting: self) { result, error in
    guard error == nil,
          let idToken = result?.user.idToken?.tokenString else { return }
    complete(.google(idToken: idToken))
}

另一个可选契约是 server auth code,由服务端向 Google 交换。两套实现中,一套只提交 ID Token;另一套可能提交授权码,取不到时回退到 ID Token。新协议最好明确标出凭证类型,避免同一字段在两种含义间切换。Google SDK 的用户取消、历史会话恢复失败和配置错误也应分开处理;缓存失效时,可清除 SDK 会话后重新发起交互授权。Google iOS 登录

LINE:Channel、回调与 OpenID

在 LINE Developers Console 创建 Provider 和 LINE Login Channel,登记 iOS 应用标识。Channel ID 用于初始化客户端 SDK;Channel Secret 留在服务端。工程接入 LINE SDK for iOS Swift 后,按官方说明配置 URL scheme 和可查询的 LINE scheme。如果启用 Universal Link,还要让 Associated Domains、站点关联文件、Channel 回调和 SDK 初始化参数保持一致;它能改善回调安全性,但必须在真机走完回跳。LINE 工程配置、Universal Link

需要 ID Token 时,请求 openid 权限。SDK 返回令牌后,业务服务端按 LINE 的要求验证受众、有效期及适用的 nonce,再建立产品会话。也可以提交 access token,由服务端先验证它,再向 LINE 取用户资料。两种契约各有对应的服务端处理,不能只把 SDK 给出的 user ID 发给后端当作登录依据。LINE 安全登录流程

LINE 案例代码

import LineSDK

// 启动时配置一次,channelID 与 callbackURL 来自环境配置。
LoginManager.shared.setup(channelID: channelID, universalLinkURL: callbackURL)
LoginManager.shared.login(permissions: [.openID], in: self) { result in
    guard case .success(let response) = result,
          let idToken = response.accessToken.IDTokenRaw else { return }
    complete(.line(idToken: idToken))
}

两套实现分别采用了不同的提交组合:一套只交 LINE ID Token,另一套同时传 access token 与资料。示例采用前一种方式;如业务确实需要资料,再增加 .profile 权限。用户取消后停留在原页面。联调时要覆盖安装与未安装 LINE、浏览器回退、URL scheme 和 Universal Link 两条回调路径。LINE iOS 集成

X:授权码与 PKCE

在 X Developer Console 创建 App 并启用 OAuth 2.0。原生 App 使用公开客户端配置,客户端只需要 Client ID,不保存 Client Secret。回调 URI 必须与控制台登记值精确一致;当前 X 文档建议正式环境使用 HTTPS 回调。采用 Universal Link 还是平台允许的其他回调方式,应先在控制台确认,再同步到 Xcode 和授权请求。权限只申请业务必需的范围。X App 配置、PKCE 流程

iOS 通过系统网页认证会话发起授权。每次请求生成新的 state、code_verifier 和 S256 challenge;回调时先核对 URI 与 state。一种清晰的分工是将授权码和 verifier 交给业务服务端,由服务端向 X 换取 access token、取得经 X 确认的用户身份,再签发产品 Token。另一种做法是在客户端完成换取后,把 X 凭证交给业务服务端;服务端仍需独立核验身份。核查的两套实现各采用其中一种;客户端换取凭证的实现还查询了用户信息,但这不能证明服务端是否省略校验。X PKCE 流程

X 案例代码

import AuthenticationServices

let (verifier, challenge) = PKCE.makeS256Pair()
let state = PKCE.randomState()
let authorizeURL = makeXAuthorizeURL(
    clientID: clientID, redirectURI: redirectURI,
    state: state, challenge: challenge, scopes: ["tweet.read", "users.read"]
)

session = ASWebAuthenticationSession(url: authorizeURL, callback: callback) { url, error in
    guard error == nil, let url,
          callbackMatches(url, redirectURI), stateMatches(url, state),
          let code = authorizationCode(from: url) else { return }
    complete(.x(code: code, verifier: verifier, redirectURI: redirectURI))
}
session?.presentationContextProvider = self
session?.start()

页面需持有 session 直到回调结束。示例中的 callback、URL 构造和解析方法要按所选回调方式实现;解析时拒绝重复参数、错误标志与空授权码。授权码有效时间很短,回调后应立即交换。用户关闭授权页、拒绝授权、state 不匹配、换取凭证失败和用户信息请求失败,应保留不同的错误阶段。示例使用的读取当前用户权限见 X 权限映射。

会话续用与失败路径

联调时将一次登录拆成三个阶段看:平台授权、业务服务端登录、登录后资料或业务初始化。平台授权失败不发起业务登录请求;用户主动取消不弹网络故障;业务服务端返回失败时不写入已登录状态。成功后再保存内部用户与产品 Token,启动受保护的业务服务。退出时停止这些服务、清理产品会话,并按需要清除平台 SDK 缓存。冷启动依赖产品会话的恢复或刷新,不能只看平台 SDK 是否记得上次用户。

两套实现都把业务服务端返回的账号信息和产品 Token 写入本地缓存,后续网络请求携带产品 Token,并通过 nonce/鉴权接口续用会话。新 App 建议把敏感会话放在 Keychain,明确过期、刷新、撤销和多设备策略。服务端是否签发 JWT、会话如何存储或撤销、平台凭证最终如何核验,没有服务端源码就不能下结论。

联调顺序

  1. 先定强制登录还是游客模式,以及跨平台绑定、游客升级、新用户资料、退出和账号删除规则。
  2. iOS 与服务端共同写定四种凭证类型、核验方式、登录响应、会话刷新和稳定错误码,准备隔离的测试环境。
  3. 在四个平台分别创建应用与凭证。团队内部维护配置映射:环境、iOS 应用标识、公开 Client/Channel ID、回调、服务端受众及保密材料负责人。
  4. 先用最小真机样例跑通平台授权和回调,再接业务登录接口。联调日志只记录平台、阶段、错误码和请求关联 ID,不记录原始凭证。
  5. 接入统一会话存储、登录门禁和业务 API;用假平台结果与假服务端覆盖成功、取消、失败、新用户及会话失效。
  6. 用正式包做四渠道真机冒烟:首次和重复授权、第三方 App 安装与否、弱网、回调冲突、切后台和账号撤销。

排查失败时,先回答三个问题:平台凭证是否拿到?业务账号接口是否接受?产品 Token 是否进入后续请求?这能把问题定位到授权回调、服务端核验或客户端会话。Google 常见的是 Client ID 与反向 scheme 不匹配;LINE 多见 Channel、scheme 或 Universal Link 配置不一致;X 要核对回调 URI、state 和 verifier;Apple 则要检查 capability、签名与令牌受众,并确认是否误以为每次都有姓名。

上线前再核对

  • 平台后台、签名、URL 回调、Associated Domains 与正式包配置一致;测试用户和正式用户的权限状态都已验证。
  • 服务端基于经过验证的稳定 subject 建立账号;客户端传来的 user ID、邮箱和头像不直接产生登录权限。
  • App 内没有 Client Secret、私钥或原始 Token 日志、埋点;会话安全存储,退出和账号删除入口可用。
  • 首次、重复、取消、拒绝、失效、弱网和服务端错误均有正确反馈,不留下永久 loading。
  • 隐私政策、数据收集说明、第三方 SDK 声明和 App Store 登录要求按目标市场及当前规则复核。Apple App Review Guidelines

四个渠道的代码可以各自封装,业务登录结果应收敛到同一处。这样平台授权失败、服务端拒绝和本地会话失效才有清楚的责任边界。