00后程序员张头像

Windows 开发 iOS,Sign in with Apple 苹果登录,KXBuild 真机调试全流程

Sign in with Apple 接入分四步:App ID 开启能力并重新生成描述文件,entitlements 加 com.apple.developer.applesignin,用 SignInWithAppleButton 拿到 identityToken 交服务端验证,删除账号时调用 revoke。

Sign in with Apple(通过 Apple 登录)接入 iOS App 分四步:给 App ID 开启 Sign in with Apple 能力并重新生成描述文件,在 entitlements 里加上 com.apple.developer.applesignin,用 SwiftUI 的 SignInWithAppleButton 拿到凭证并把 identityToken 交给服务端验证,最后处理授权撤销和账号删除时的 revoke。

下文从开发者后台配置一直讲到在自己的 iPhone 上登录成功,Apple 规则截至 2026 年 10 月,都附了官方链接。开始前准备好这几样东西。

  • 已加入 Apple Developer Program 的账号,角色是账户持有人或管理员,开启能力需要这两种角色之一。
  • 一个能用 KXBuild 装到 iPhone 上运行的 SwiftUI 工程,部署目标 iOS 15 或更高。还没有的按 [SwiftUI 从新建工程到装上 iPhone]做一个。
  • 一部登录了 Apple 账户、开启了双重认证的 iPhone,并打开了开发者模式,开关位置见[开发者模式怎么打开]。
  • 一个你自己的后端接口,用来验证登录凭证、建立账号。文中用 https://api.example.com/auth/apple 代替。

什么时候必须提供「通过 Apple 登录」?

App Store 审核指南 [4.8 登录服务]规定,App 用第三方或社交登录(例如 Facebook、Google、微信登录)来创建或验证用户的主账号时,必须同时提供另一种等效的登录方式。这种登录方式要满足三条:只收集用户的姓名和邮箱;允许用户在建账号时隐藏邮箱;未经同意不收集用户在 App 里的行为用于广告。「通过 Apple 登录」满足这三条,是最常见的选择。

下面这些情况不需要另外提供。

情况例子
只用你公司自己的账号体系手机号加验证码、自家用户名密码
教育、企业或商务 App,要求用已有的学校或企业账号登录企业内部办公 App
用政府或行业背书的公民身份或电子身份认证电子身份证登录
App 是某个第三方服务的客户端,用户要直接登录那个服务才能看到内容邮件客户端、某个社交平台的专用客户端
替代应用市场,或从替代应用市场分发、使用该市场的专用登录—

另外两条和登录相关的规定也要一起满足。审核指南 [5.1.1(v) 要求支持创建账号的 App 必须在 App 内提供删除账号的入口。

Apple 的[账号删除说明]写明,使用「通过 Apple 登录」的 App 在用户删除账号时,要调用 Sign in with Apple REST API 撤销用户的令牌,做法见下文 [revoke 一节]。审核由 Apple 决定,常见的被拒原因见 [App 审核被拒的常见原因]。

接入 Sign in with Apple 要做哪几步?

步骤在哪里做产出
1. App ID 开启 Sign in with Appledeveloper.apple.com 的 IdentifiersApp ID 带上这项能力
2. 重新生成描述文件developer.apple.com 的 Profiles含 com.apple.developer.applesignin 的描述文件
3. 加 entitlementsVS CodeMyApp.entitlements
4. 写登录代码VS Code登录按钮、凭证处理、状态检查
5. 创建 Sign in with Apple 私钥developer.apple.com 的 Keys服务端用来生成 client_secret 的 .p8
6. 服务端验证和 revoke你的后端验证 identityToken、换取和撤销令牌
7. 真机测试iPhone第一次登录、再次登录、撤销后重新登录

第 1 步:App ID 怎么开启 Sign in with Apple?

  1. 登录 developer.apple.com,进入 Certificates, Identifiers & Profiles,在 Identifiers 页点开 App 的 App ID。App ID 要是精确的 Bundle ID,例如 com.example.myapp。
  2. 在 Capabilities 列表勾选 Sign in with Apple,点旁边的 Edit 或 Configure。
  3. 选 Enable as a primary App ID(作为主 App ID 启用),保存。第一次启用、或者这是一个新 App 时都选这一项。
  4. 点 Save 保存 App ID。

同一个产品还有 Mac 版、网页版要共用账号的,那些 App ID 选「与已有的主 App ID 分组」,用户只需授权一次。规则见 Apple 的[关于通过 Apple 登录]。

第 2 步:为什么要重新生成描述文件?

描述文件里记录着 App ID 当时开通的能力,开启 Sign in with Apple 之前生成的描述文件不含这项能力,必须重新生成。

  1. 在 Profiles 页逐个点开这个 App ID 的描述文件(Development、Ad Hoc、App Store),点 Edit,直接 Save,再 Download。
  2. 用新文件替换工程 keychain 目录里的旧文件,旧的删掉。目录里同时留着新旧两份时,构建可能匹配到不含这项能力的旧文件。

在 VS Code 里双击新的 .mobileprovision 用描述文件查看器打开,权限里出现 com.apple.developer.applesignin 就说明已经包含这项能力。证书目录的放法和挑选规则见[证书与签名],App ID 和描述文件的关系见 [Bundle ID、App ID、证书和描述文件的关系]。

第 3 步:给工程加 entitlements

在 App 源码文件夹里新建 MyApp.entitlements,内容如下。工程原本已有 entitlements 文件的(例如开了推送或 App Groups),把这个键加进原文件,不用新建。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>com.apple.developer.applesignin</key>
	<array>
		<string>Default</string>
	</array>
</dict>
</plist>

键名和取值以 Apple 的 [applesignin entitlement 文档]为准,值是只含一项 Default 的数组。

然后让 target 用这个文件。以下两种做法任选其一。

  • VS Code 插件。右键 target 选「KXBuild: 编辑目标设置」,在 General 页 Signing 分组的「Entitlements 文件」里填 MyApp/MyApp.entitlements,Ctrl+S 保存。
  • 命令行。在工程目录执行下面这条,路径相对于 .xcodeproj 所在目录。
kxbuild project add-setting -k CODE_SIGN_ENTITLEMENTS -v MyApp/MyApp.entitlements -t MyApp -P .\MyApp.xcodeproj

以后要加其他能力,双击 .entitlements 用[属性列表编辑器]编辑即可。设置项的位置见[工程设置编辑器]。

第 4 步:用 Swift 写登录代码

下面三个文件适合部署目标 iOS 15 及以上,在 Swift 6 严格并发模式下也能编译。SignInWithAppleButton 来自 SwiftUI,凭证类型来自 AuthenticationServices 框架。

用钥匙串保存用户标识

用户标识要存在钥匙串里,不要放 UserDefaults。新建 KeychainStore.swift。

import Foundation
import Security

/// 把少量字符串存进钥匙串,删 App 重装后通常还在
enum KeychainStore {
    private static let service = "com.example.myapp.auth"

    static func save(_ value: String, for key: String) {
        delete(key)
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: service,
            kSecAttrAccount as String: key,
            kSecValueData as String: Data(value.utf8),
            kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock,
        ]
        SecItemAdd(query as CFDictionary, nil)
    }

    static func read(_ key: String) -> String? {
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: service,
            kSecAttrAccount as String: key,
            kSecReturnData as String: true,
            kSecMatchLimit as String: kSecMatchLimitOne,
        ]
        var item: CFTypeRef?
        guard SecItemCopyMatching(query as CFDictionary, &item) == errSecSuccess,
              let data = item as? Data else { return nil }
        return String(data: data, encoding: .utf8)
    }

    static func delete(_ key: String) {
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: service,
            kSecAttrAccount as String: key,
        ]
        SecItemDelete(query as CFDictionary)
    }
}

登录管理器

新建 AppleSignInManager.swift,负责准备请求、处理凭证、把令牌发给服务端、检查凭证状态。把 loginURL 换成你自己的接口。

import Foundation
import AuthenticationServices
import CryptoKit

/// 发给自己服务器的登录请求体
struct AppleLoginRequest: Codable {
    let identityToken: String
    let authorizationCode: String
    let rawNonce: String
    let user: String
    let email: String?
    let givenName: String?
    let familyName: String?
}

@MainActor
final class AppleSignInManager: ObservableObject {
    // 换成你自己服务器的登录接口
    static let loginURL = URL(string: "https://api.example.com/auth/apple")!

    @Published private(set) var userID: String? = KeychainStore.read("appleUserID")
    @Published var message: String?

    private var currentNonce: String?
    private var revokeObserver: NSObjectProtocol?

    var isSignedIn: Bool { userID != nil }

    init() {
        // 用户在系统设置里停用了「通过 Apple 登录」时,系统发这个通知
        revokeObserver = NotificationCenter.default.addObserver(
            forName: ASAuthorizationAppleIDProvider.credentialRevokedNotification,
            object: nil,
            queue: .main
        ) { [weak self] _ in
            Task { @MainActor in
                self?.signOut(message: "Apple 账户授权已撤销,请重新登录")
            }
        }
    }

    /// 点按钮时调用:要姓名和邮箱,并带上 nonce
    func prepare(_ request: ASAuthorizationAppleIDRequest) {
        let nonce = Self.randomNonce()
        currentNonce = nonce
        request.requestedScopes = [.fullName, .email]
        request.nonce = Self.sha256(nonce)
    }

    /// 处理按钮的回调结果
    func handle(_ result: Result<ASAuthorization, Error>) async {
        switch result {
        case .success(let authorization):
            guard let credential = authorization.credential as? ASAuthorizationAppleIDCredential else {
                message = "收到的不是 Apple ID 凭证"
                return
            }
            await signIn(with: credential)
        case .failure(let error):
            if let authError = error as? ASAuthorizationError, authError.code == .canceled {
                return // 用户自己点了取消,不提示
            }
            message = "登录失败:\(error.localizedDescription)"
        }
    }

    private func signIn(with credential: ASAuthorizationAppleIDCredential) async {
        // user 是这个用户在你的开发者团队下固定不变的标识,用它当账号主键
        let user = credential.user

        // 邮箱和姓名只在第一次授权时返回,拿到就先存下来
        if let email = credential.email {
            KeychainStore.save(email, for: "appleEmail")
        }
        if let name = credential.fullName {
            let formatted = PersonNameComponentsFormatter().string(from: name)
            if !formatted.isEmpty {
                KeychainStore.save(formatted, for: "appleFullName")
            }
        }

        guard let tokenData = credential.identityToken,
              let identityToken = String(data: tokenData, encoding: .utf8),
              let codeData = credential.authorizationCode,
              let authorizationCode = String(data: codeData, encoding: .utf8),
              let rawNonce = currentNonce else {
            message = "没有拿到 identityToken,请重试"
            return
        }

        let body = AppleLoginRequest(
            identityToken: identityToken,
            authorizationCode: authorizationCode,
            rawNonce: rawNonce,
            user: user,
            email: credential.email ?? KeychainStore.read("appleEmail"),
            givenName: credential.fullName?.givenName,
            familyName: credential.fullName?.familyName
        )

        do {
            var request = URLRequest(url: Self.loginURL)
            request.httpMethod = "POST"
            request.setValue("application/json", forHTTPHeaderField: "Content-Type")
            request.httpBody = try JSONEncoder().encode(body)
            let (_, response) = try await URLSession.shared.data(for: request)
            guard (response as? HTTPURLResponse)?.statusCode == 200 else {
                message = "服务器验证失败"
                return
            }
            KeychainStore.save(user, for: "appleUserID")
            userID = user
            message = nil
        } catch {
            message = "网络错误:\(error.localizedDescription)"
        }
    }

    /// 启动时和回到前台时调用,确认授权还有效
    func checkCredentialState() async {
        guard let userID else { return }
        do {
            let state = try await ASAuthorizationAppleIDProvider().credentialState(forUserID: userID)
            switch state {
            case .authorized:
                break
            case .revoked:
                signOut(message: "Apple 账户授权已撤销,请重新登录")
            case .notFound:
                signOut(message: "请重新登录")
            case .transferred:
                message = "App 已转移到新的开发者团队,需要迁移账号"
            @unknown default:
                break
            }
        } catch {
            // 查询失败(例如没登录 Apple 账户)时保持现状,下次再查
            print("getCredentialState 失败: \(error)")
        }
    }

    func signOut(message: String? = nil) {
        KeychainStore.delete("appleUserID")
        userID = nil
        self.message = message
    }

    // MARK: - nonce

    static func randomNonce(length: Int = 32) -> String {
        let charset = Array("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-._")
        var generator = SystemRandomNumberGenerator()
        return String((0..<length).map { _ in charset.randomElement(using: &generator)! })
    }

    static func sha256(_ input: String) -> String {
        SHA256.hash(data: Data(input.utf8)).map { String(format: "%02x", $0) }.joined()
    }
}

几个要点。

  • user 当账号主键。Apple 的文档写明,用户标识在你的开发者团队下唯一且不变,要用它而不是邮箱来识别用户。用户隐藏了邮箱时,你拿到的是一个 privaterelay.appleid.com 的转发地址。
  • 姓名和邮箱只给一次。按 Apple 的[用户认证说明],姓名和邮箱只在用户第一次用「通过 Apple 登录」授权你的 App 时返回,之后在别的设备上登录也不会再给。用户停用后重新授权才会再次返回。
  • 拿到就保存。拿到后要立刻保存,并尽快传给服务端,网络失败时下次还能补发。之后的 identityToken 里仍带邮箱,但不带姓名。
  • nonce 防重放。请求里放的是随机串的 SHA-256,identityToken 里的 nonce 字段就是这个哈希值。原始随机串发给服务端,服务端算一次 SHA-256 再比对。
  • .canceled 不当错误。用户在登录框点了取消会走到这里,不用弹提示。

登录界面和 App 入口

新建 LoginView.swift,Swift 工程的基本配置见 [Windows 开发 Swift iOS App]。按钮的样式跟随深色、浅色模式切换,深色背景上用白色按钮。

import SwiftUI
import AuthenticationServices

struct LoginView: View {
    @EnvironmentObject private var auth: AppleSignInManager
    @Environment(\.colorScheme) private var colorScheme

    var body: some View {
        VStack(spacing: 20) {
            if auth.isSignedIn {
                Text("已登录")
                    .font(.title2.bold())
                Text(KeychainStore.read("appleFullName") ?? "Apple 用户")
                Text(KeychainStore.read("appleEmail") ?? "未提供邮箱")
                    .foregroundStyle(.secondary)
                Button("退出登录") {
                    auth.signOut()
                }
            } else {
                SignInWithAppleButton(.signIn) { request in
                    auth.prepare(request)
                } onCompletion: { result in
                    Task { await auth.handle(result) }
                }
                .signInWithAppleButtonStyle(colorScheme == .dark ? .white : .black)
                .frame(height: 50)
            }

            if let message = auth.message {
                Text(message)
                    .font(.footnote)
                    .foregroundStyle(.red)
            }
        }
        .padding(24)
    }
}

把 App 入口文件(例如 MyAppApp.swift)改成下面这样。启动时和每次回到前台时都检查一次凭证状态。

import SwiftUI

@main
struct MyAppApp: App {
    @StateObject private var auth = AppleSignInManager()
    @Environment(\.scenePhase) private var scenePhase

    var body: some Scene {
        WindowGroup {
            LoginView()
                .environmentObject(auth)
                .task { await auth.checkCredentialState() }
        }
        .onChange(of: scenePhase) { phase in
            if phase == .active {
                Task { await auth.checkCredentialState() }
            }
        }
    }
}

部署目标是 iOS 17 及以上的,onChange 可以改用两个参数的新写法 { _, phase in ... },旧写法会有弃用警告但不影响运行。UIKit 工程用 ASAuthorizationAppleIDButton 加 ASAuthorizationController,凭证处理部分和上面相同,写法见 Apple 的[示例项目]。

凭证里有什么

字段内容什么时候有
user用户标识,团队内唯一且不变每次都有
email真实邮箱或转发邮箱只在第一次授权时有
fullName姓名,用户可以在授权框里改只在第一次授权时有
identityTokenJWT,交给服务端验证每次都有
authorizationCode一次性授权码,服务端拿它换 refresh token每次都有
realUserStatus是否像真人,likelyReal、unknown、unsupported只在第一次授权时返回

用户停用授权后 App 怎么知道?

用户可以随时在手机设置里停用你的 App 的「通过 Apple 登录」,所以 App 要主动确认授权还在。getCredentialState(forUserID:completion:) 在 Swift 里可以写成 try await credentialState(forUserID:),这是一个本地调用,不走网络,可以放心在每次回到前台时调用。Apple 的[验证用户]一文建议把 App 的登录会话和这个调用的结果绑在一起。

状态含义处理
.authorized授权有效继续使用
.revoked用户已撤销授权退出登录,回到登录页
.notFound这个用户和你的 App 没有授权关系退出登录,回到登录页
.transferredApp 已转移到其他开发者团队按 Apple 的团队转移流程迁移用户标识

App 在运行时,用户撤销授权,系统会发出 credentialRevokedNotification,代码里已经监听它并退出登录。用户删除 Apple 账户时系统不发这个通知,Apple 文档要求用 getCredentialState 来发现,所以前台检查不能省。

服务端怎么验证 identityToken?

App 拿到的凭证必须在服务端验证后才能建立登录会话。服务端要做三件事:验证 identityToken、用 authorizationCode 换 refresh token、在用户删除账号时撤销令牌。

验证 identityToken 的五项检查

检查怎么做
签名从 https://appleid.apple.com/auth/keys 取 Apple 的公钥集合,用 JWT 头里 kid 对应的那把公钥、按头里的 alg 验签
iss必须是 https://appleid.apple.com
aud必须是你的 client_id,iOS App 就是它的 Bundle ID
exp当前时间要早于过期时间
nonce等于 App 发来的原始随机串的 SHA-256

通过后再核对 sub 字段等于 App 发来的 user,以 sub 作为账号主键。检查项出自 Apple 的[验证用户]。公钥集合里可能有多把,数量也会变,不要写死,按 kid 挑选并缓存一段时间。

创建 Sign in with Apple 私钥

服务端调用 Apple 的令牌接口要带 client_secret,它是一个用 Sign in with Apple 私钥签名的 JWT。

  1. 在 Certificates, Identifiers & Profiles 的 Keys 页点「+」,填名字,勾选 Sign in with Apple,点旁边的 Configure。
  2. 选择关联的主 App ID,保存后点 Continue、Register。
  3. 下载 .p8 文件,记下 Key ID。这个文件同样只能下载一次。

每个主 App ID 最多关联两把私钥,见 Apple 的[创建 Sign in with Apple 私钥]。这把私钥和推送用的 APNs 密钥是两回事,推送密钥的区别见 [推送用 .p8 还是 .p12]。

client_secret 的 JWT 内容如下,用 ES256 签名。

{
  "alg": "ES256",
  "kid": "你的 Key ID"
}
{
  "iss": "你的 Team ID",
  "iat": 1792000000,
  "exp": 1807000000,
  "aud": "https://appleid.apple.com",
  "sub": "com.example.myapp"
}

sub 填 client_id,也就是 Bundle ID,区分大小写。exp 最多比当前时间晚 15777000 秒(六个月),超过会被拒,规则见 Apple 的[创建 client secret]。可以每几个月在服务端重新生成一次。

用授权码换 refresh token

curl -X POST "https://appleid.apple.com/auth/token" \
  -H 'content-type: application/x-www-form-urlencoded' \
  -d 'client_id=com.example.myapp' \
  -d 'client_secret=上一步生成的 JWT' \
  -d 'code=App 发来的 authorizationCode' \
  -d 'grant_type=authorization_code'

成功时返回 access_token、refresh_token 和 id_token。把 refresh_token 和账号一起存在服务端,删除账号时要用它。授权码只能用一次,App 每次登录拿到的都是新的。接口说明见 Apple 的[令牌验证]。

Apple 允许你最多每天用 refresh token 验证一次用户的 Apple 账户是否仍然正常,调用太频繁会被限流。

用户删除账号时怎么调用 revoke?

用户在 App 里删除账号时,服务端删除数据后再调用撤销接口,让这个用户对你的 App 的授权失效。

curl -X POST "https://appleid.apple.com/auth/revoke" \
  -H 'content-type: application/x-www-form-urlencoded' \
  -d 'client_id=com.example.myapp' \
  -d 'client_secret=同样的 client_secret JWT' \
  -d 'token=保存的 refresh_token' \
  -d 'token_type_hint=refresh_token'

成功时返回 200,没有响应体,令牌之前已经失效的也返回 200。没存 refresh token 的,可以用 access token 撤销,token_type_hint 改成 access_token。接口见 Apple 的[令牌撤销]。撤销后,用户下次再用「通过 Apple 登录」进你的 App,会像第一次一样重新授权姓名和邮箱。

用户也可能在 App 外面停用授权或删除 Apple 账户。在 App ID 的 Sign in with Apple 配置里登记一个服务器通知地址,Apple 会在这些事件发生时推送通知,事件类型包括 consent-revoked(停用授权)、account-deleted(删除 Apple 账户)、email-disabled 和 email-enabled(转发邮箱开关)。格式见 Apple 的[处理账户变更]。

用户选了隐藏邮箱、你又要给他发邮件的,要先在开发者后台登记发件域名或发件地址,并配好 SPF 或 DKIM,否则转发服务不会投递。

怎么在 iPhone 上测试通过 Apple 登录?

第一次登录

  1. 在 KXBuild 视图的「构建配置」节点选 Debug,运行 KXBuild: 构建并运行,装到手机上。调试方法见 [Windows 上真机调试 iOS App]。
  2. 点「通过 Apple 登录」按钮,系统弹出授权框,显示姓名和邮箱选项。可以改名字,邮箱可以选「共享我的电子邮件」或「隐藏邮件地址」。
  3. 用面容 ID、触控 ID 或密码确认。App 显示「已登录」和姓名、邮箱。

在代码里 signIn(with:) 的开头打一个断点,能看到 email、fullName 有值。服务端还没写好时,可以先把 loginURL 的请求注释掉,直接保存 user,把 App 端跑通。

再次登录

点「退出登录」后再点按钮,这次授权框只有一个「继续」,断点里 email 和 fullName 都是 nil。这是正常行为,说明第一次就要把它们存好。

撤销授权后重新测试第一次登录

想再看一次第一次授权的完整流程,按 Apple 的[管理使用「通过 Apple 登录」的 App]操作。

  1. 打开手机「设置」,轻点最上方你的姓名。
  2. 轻点「通过 Apple 登录」。
  3. 在列表里选中你的 App,轻点「删除」,按提示确认。
  4. 回到 App,App 回到前台时 checkCredentialState() 拿到 .revoked,自动退出登录。
  5. 再点登录按钮,授权框又会出现姓名和邮箱选项。

只是删掉 App 重装,不会重置授权,用户标识和「只给一次」的规则都不变。

Sign in with Apple 常见报错怎么处理?

现象原因处理
点按钮后报错 ASAuthorizationError 1000(unknown)签名里没有 com.apple.developer.applesignin,多见于 App ID 没开能力、描述文件没重新生成,或 target 没用上 entitlements 文件双击 .mobileprovision 确认有这项能力,在工程设置编辑器里搜索 CODE_SIGN_ENTITLEMENTS 确认路径,重新构建
系统提示去「设置」里登录 Apple 账户手机没登录 Apple 账户,或账户没开双重认证在「设置」里登录 Apple 账户并开启双重认证后再试
报 1001(canceled)用户点了取消,或授权框被关掉不用提示,让用户再点一次
报 1004(failed)、1002(invalidResponse)授权过程失败或响应异常稍后重试,网络受限时换网络
构建报描述文件不含 com.apple.developer.applesignin描述文件是开启能力前生成的编辑描述文件后保存,重新下载替换旧文件
安装报 signed with invalid entitlementsentitlements 里有这个键,描述文件里没有同上,见[签名报错]
email、fullName 是 nil不是第一次授权按上文撤销授权后重测;正式 App 里从自己保存的数据读取
服务端验证 aud 不通过client_id 写成了 Services ID 或别的 Bundle IDiOS App 登录时 aud 是 App 的 Bundle ID
/auth/token 返回 invalid_clientclient_secret 过期、签名错、sub 和 client_id 不一致,或 Key ID、Team ID 填错重新生成 client_secret,核对 kid、iss、sub
/auth/token 返回 invalid_grant授权码已用过或已过期每次登录都用 App 新拿到的授权码,收到后立即换取

错误码数值以 SDK 里的 ASAuthorizationError.Code 为准,接口错误的含义见 Apple 的 [ErrorResponse]。Sign in with Apple 之外的签名和描述文件报错见 [iOS 签名报错与 ipa 安装失败]。

常见问题

App 有微信登录,必须加「通过 Apple 登录」吗?

按审核指南 4.8,用第三方或社交登录建立主账号的 App,要同时提供一种只收集姓名和邮箱、允许隐藏邮箱、不做广告追踪的登录方式。「通过 Apple 登录」满足这些条件,是最常用的做法;只用自家账号体系、企业或教育账号等情况可以不加。

为什么第二次登录拿不到邮箱和姓名?

Apple 只在用户第一次授权你的 App 时返回姓名和邮箱,之后的登录只返回用户标识和令牌。第一次拿到就要保存并传给服务端,identityToken 里之后仍带邮箱,但不带姓名。

测试时怎么重置「通过 Apple 登录」的授权?

在 iPhone 的「设置 → 你的姓名 → 通过 Apple 登录」里选中 App,轻点「删除」。之后再登录,系统会重新询问姓名和邮箱。

用户删除账号时要做什么?

在 App 内提供删除账号入口,服务端删除数据后,用保存的 refresh token 调用 https://appleid.apple.com/auth/revoke 撤销授权。Apple 要求使用「通过 Apple 登录」的 App 这样做。

ASAuthorizationError 1000 是什么原因?

1000 是 unknown 错误,最常见的原因是签名里缺少 com.apple.developer.applesignin:App ID 没开能力、描述文件没重新生成,或 target 没设置 entitlements 文件。签名都正确仍然报错时,删掉 App 后用新描述文件重新构建安装再试。

更多教程可以查看官网:https://www.kxbuild.net/zh/tutorial/201

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/2501_91373349/article/details/170194892

文章来源转载

评论

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--