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)中声明收集的资料类型。