iOS 接入

使用 ASWebAuthenticationSession 承载授权页(系统级浏览器,可共享登录态),回调通过 Universal Link 或自定义 scheme 返回。

1. 配置

  • 回调地址登记 jintuziopen://oauth/callback 或 https://your-domain.com/oauth/callback;
  • 若用 Universal Link,需要在 Associated Domains 里添加 applinks:your-domain.com,并在服务端部署 apple-app-site-association。

2. 发起授权(Swift)

import AuthenticationServices
import CryptoKit

func startOAuth() {
    let verifier = randomURLSafeString(length: 64)
    let challenge = Data(SHA256.hash(data: Data(verifier.utf8)))
        .base64URLEncodedString()
    UserDefaults.standard.set(verifier, forKey: "pkce_verifier")
    let state = UUID().uuidString
    UserDefaults.standard.set(state, forKey: "oauth_state")

    var comps = URLComponents(string: "https://open.jintuzi.com/oauth/authorize")!
    comps.queryItems = [
        .init(name: "response_type", value: "code"),
        .init(name: "client_id", value: "你的_app_id"),
        .init(name: "redirect_uri", value: "jintuziopen://oauth/callback"),
        .init(name: "scope", value: "user:avatar user:nickname user:jintuzi_no"),
        .init(name: "state", value: state),
        .init(name: "code_challenge", value: challenge),
        .init(name: "code_challenge_method", value: "S256")
    ]

    let session = ASWebAuthenticationSession(
        url: comps.url!,
        callbackURLScheme: "jintuziopen"
    ) { url, error in
        guard let url,
              let items = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems,
              let code = items.first(where: { $0.name == "code" })?.value
        else { return }
        exchange(code: code)
    }
    session.presentationContextProvider = self
    session.prefersEphemeralWebBrowserSession = false
    session.start()
}

3. 换取 token

var req = URLRequest(url: URL(string: "https://open.jintuzi.com/oauth/token")!)
req.httpMethod = "POST"
req.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
let verifier = UserDefaults.standard.string(forKey: "pkce_verifier")!
req.httpBody = [
  "grant_type=authorization_code",
  "client_id=你的_app_id",
  "code=\(code)",
  "redirect_uri=jintuziopen://oauth/callback",
  "code_verifier=\(verifier)"
].joined(separator: "&").data(using: .utf8)

4. 注意

  • iOS 上不要用 WKWebView 打开授权页——用户无法确认域名,且 App Store 审核通常会拒;
  • token 存 Keychain,不要放 UserDefaults;
  • App Store 隐私清单(PrivacyInfo.xcprivacy)中声明收集的资料类型。