带有名称和邮件范围的 ASAuthorizationAppleIDRequest 返回 nil 值

Posted

技术标签:

【中文标题】带有名称和邮件范围的 ASAuthorizationAppleIDRequest 返回 nil 值【英文标题】:ASAuthorizationAppleIDRequest with name and mail scope returns nil values 【发布时间】:2019-12-26 19:37:45 【问题描述】:

我正在实施 Sign in with Apple 并注意到返回的 ASAuthorizationAppleIDCredentialemailfullName 属性仅在此 Apple ID 的第一次登录时填写。在所有后续登录中,这些属性都为零。

这是 ios 13 上的错误还是预期行为?

这是我用来启动请求的代码:

@available(iOS 13.0, *)
dynamic private func signInWithAppleClicked() 
    let request = ASAuthorizationAppleIDProvider().createRequest()
    request.requestedScopes = [.fullName, .email]

    let controller = ASAuthorizationController(authorizationRequests: [request])
    controller.delegate = self
    controller.presentationContextProvider = self
    controller.performRequests()

我在这个委托方法中收到了证书:

public func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) 
    guard let credential = authorization.credential as? ASAuthorizationAppleIDCredential else  return 

    let userIdentifier = credential.user
    let token = credential.identityToken
    let authCode = credential.authorizationCode
    let realUserStatus = credential.realUserStatus
    let mail = credential.email // nil
    let name = credential.fullName // nil

【问题讨论】:

我在这上面花了将近 3 个小时,好像是个 bug。用户第一次创建帐户时 - 只有这样我才能获取全名和电子邮件。在随后的登录尝试中,它只用 .user (id) 返回我,其他一切都返回 nil。 根据这个线程 (forums.developer.apple.com/message/377782#377437) 这似乎是预期的行为。 类似问题***.com/q/57714339/2595805 这是 Apples 文档中详述的预期行为 - developer.apple.com/documentation/signinwithapplerestapi/… 所以听起来如果用户在不同的设备上再次登录到同一个应用程序(或同一个设备,但应用程序已被删除),用户的信息(例如,完整name) 必须从服务器向下传播到应用程序。 (或者如下所示,可能来自本地解码 JWT?) 【参考方案1】:

似乎是一个错误,但在阅读了苹果论坛上的不同帖子后,这似乎是预期的行为。

所以一些外卖。

    首次使用 Apple 登录(注册)时,请确保在您的后端创建用户帐户。

    如果您的服务器出现任何连接错误,请确保将用户详细信息保存在本地(因为您下次不会收到此信息)并继续重试以在您的后端创建帐户。

    为了在设备上进行测试,您可以撤销您的应用程序的 Apple ID 登录。撤销后它将像下次注册一样工作,您将获得诸如(电子邮件、姓名等)之类的详细信息。

使用 IOS 13 撤消您设备上的访问权限。

iPhone Settings > Apple Id > Password & Security > Apple ID logins > YOUR APP > Stop using Apple ID

【讨论】:

这会使触摸 id 的屏幕或任何其他屏幕对其进行身份验证,不再调用...只是不再起作用...任何想法?? 如果我只能在第一次通话时收到电子邮件和全名,如果之后全名发生变化,我可以得到通知吗? @mdonati : 从 iOS 14 开始,您可以通过实现 S2S 通知来获取此信息。目前,我们没有关于这些通知的更多信息。【参考方案2】:

如果您想知道如何在第二次及以后检索电子邮件,这里有一个提示:使用 identityToken,其中包含在 JWT 中编码的用户授权数据,包括电子邮件。

    导入这个库来解码 JWT:https://github.com/auth0/JWTDecode.swift 试试这个代码
    import JWTDecode
    // ...
    if let identityTokenData = appleIDCredential.identityToken,
    let identityTokenString = String(data: identityTokenData, encoding: .utf8) 
    print("Identity Token \(identityTokenString)")
    do 
       let jwt = try decode(jwt: identityTokenString)
       let decodedBody = jwt.body as Dictionary<String, Any>
       print(decodedBody)
       print("Decoded email: "+(decodedBody["email"] as? String ?? "n/a")   )
     catch 
       print("decoding failed")
    

或者像这样在 php 后端对其进行解码:

    print_r(json_decode(base64_decode(str_replace('_', '/', str_replace('-','+',explode('.', $identityTokenString)[1])))));

【讨论】:

只有在第一次登录时,JWT 才会包含电子邮件。绝不是有关用户的名称或其他信息。 developer.apple.com/documentation/sign_in_with_apple/… 不正确。我能够一直在 identityToken 中以编码表示形式发送电子邮件。 我复制粘贴了您的代码,只是第一次包含电子邮件。 我尝试的时候,邮件也是在后续的identityToken响应中发送的。也许值得注意的是,我选择隐藏我的电子邮件地址,并且令牌包含那个苹果中继邮件地址...... 好吧,感谢像你这样的人,我还有工作:)。复制粘贴通常不起作用,您必须挖掘一下。这个令牌现在可以在后端使用 JWKFactory php 库的生产应用程序上完美运行。【参考方案3】:

这似乎是预期的行为:

这行为正确,用户信息仅在 初始用户注册时的 ASAuthorizationAppleIDCredential。随后的 使用 Sign In with Apple 使用相同帐户登录您的应用 不共享任何用户信息,只会在 ASAuthorizationAppleIDCredential。建议您安全地 缓存包含用户的初始 ASAuthorizationAppleIDCredential 信息,直到您可以验证帐户已成功 在您的服务器上创建。

来源https://forums.developer.apple.com/thread/121496#379297

【讨论】:

【参考方案4】:

这是使用 Apple 实现 SignIn 时的正确行为。

这行为正确,用户信息仅在 ASAuthorizationAppleIDCredential 初始用户注册时。随后的 使用 Sign In with Apple 使用相同帐户登录您的应用 不共享任何用户信息,只会在 ASAuthorizationAppleIDCredential。建议您安全地 缓存包含用户的初始 ASAuthorizationAppleIDCredential 信息,直到您可以验证帐户已成功 在您的服务器上创建。

为了解决这个问题,我们可以将所有需要的信息存储在Keychain 中。我为 SignIn With Apple 创建了 Singleton 类。我相信它会对你有所帮助。

Git 源:https://github.com/IMHitesh/HSAppleSignIn

您需要按照以下步骤操作:

步骤:1

将 AppleSignIn 文件夹添加到您的项目中。

步骤:2

Capabilities 中启用 Apple 登录。

步骤:3 -> 重要

转到您的UIViewController 并添加用于登录的容器视图 苹果。

if #available(iOS 13.0, *) 
    appleSignIn.loginWithApple(view:viewAppleButton, completionBlock:  (userInfo, message) in
        if let userInfo = userInfo
            print(userInfo.email)
            print(userInfo.userid)
            print(userInfo.firstName)
            print(userInfo.lastName)
            print(userInfo.fullName)
        else if let message = message
            print("Error Message: \(message)")
        else
            print("Unexpected error!")
        
    )
else
    viewAppleButton.isHidden = true

【讨论】:

【参考方案5】:

这不是错误,但它表明您的身份验证已成功存储到您的设备设置中。

如果你想再次获得所有信息,那么你需要遵循这些状态。

    转到您的设备 -> 设置 -> Apple ID -> 密码和安全性 -> 使用您的 Apple ID 的应用程序 -> 您将获得使用 Apple 登录的应用程序列表 查找您的应用程序 -> 在您的应用程序行左侧快速显示删除选项 -> 点击删除

    重新启动您的应用或使用苹果按钮重新登录

现在您可以获得所有信息

【讨论】:

【参考方案6】:

https://developer.apple.com/documentation/signinwithapplerestapi/authenticating_users_with_sign_in_with_apple 中写道:

由于用户信息不会在任何后续 API 调用中与您的应用共享,因此您的应用应在您从 API 响应中收到信息后立即将其存储在本地。如果您的进程或网络随后出现故障,您可以从本地存储中读取信息并尝试再次处理。

【讨论】:

【参考方案7】:

电子邮件将在第一次登录时给出。如果用户没有“撤销”您应用的苹果登录(在用户的系统设置页面的 Apple ID 中),则登录回调将是返回一个零电子邮件值。可以保存第一次登录成功结果的用户名和邮箱信息,下次登录时判断返回与保存信息的区别。

更好的做法是在您的应用处于“活动状态”时判断 ASAuthorizationAppleIDProvider.getCredentialState 的值,以便及时与后端服务器同步登录状态。

请参考:How to Sign Out of Apple After Being Authenticated

【讨论】:

【参考方案8】:

我为此问题编写了一个 Helper 类。这个 Helper 类可以帮助在 keyChain 之间安全地保存和检索用户信息。 我正在使用SwiftKeychainWrapper 库为我完成繁重的任务。随意将帮助程序类复制粘贴到您的代码中。您可能需要根据需要添加任何其他额外信息。

import Foundation
import SwiftKeychainWrapper

/// A Helper class which abstract Keychain API related calls.
final class KeyChainService 
    // MARK: - Properties
    static let shared = KeyChainService()
    
    /// Returns previous saved user name if available.
    var appleUserName: String? 
        return KeychainWrapper
            .standard
            .string(forKey: .appAppleUserName)
    
    
    /// Returns previous saved user appleId/email  if available.
    var appleUserEmail: String? 
        return KeychainWrapper
            .standard
            .string(forKey: .appAppleEmailId)
    
    
    
    /// Saves the apple user name into keychain.
    /// - Parameter name: Apple user name retrieved form AppleLogin.
    /// - Returns: true if succeed otherwise false.
    @discardableResult
    func saveAppleUserName(name: String?) -> Bool 
        guard let name = name else return false
        return KeychainWrapper.standard.set(
            name,
            forKey: KeychainWrapper.Key.appAppleUserName.rawValue
        )
    
    
    /// Saves the apple user email into keychain.
    /// - Parameter email: Apple userId/email  retrieved form AppleLogin.
    /// - Returns: true if succeed otherwise false.
    @discardableResult
    func saveAppleEmail(email: String?) -> Bool 
        guard let email = email else return false
        return KeychainWrapper.standard.set(
            email,
            forKey: KeychainWrapper.Key.appAppleEmailId.rawValue
        )
    
    

    /// Deletes both apple user name and saved Id from keyChain.
    func deleteSavedAppleUserInfo()
        KeychainWrapper.standard.remove(forKey: .appAppleUserName)
        KeychainWrapper.standard.remove(forKey: .appAppleEmailId)
    


// MARK: - KeychainWrapper + Extensions
extension KeychainWrapper.Key 
    /// A random string used to identify saved user apple name from keychain.
    static let appAppleUserName: KeychainWrapper.Key = "appAppleUserName"
    
    /// A random string used to identify saved user apple email /Id from keychain.
    static let appAppleEmailId:KeychainWrapper.Key = "appAppleEmailId"


【讨论】:

以上是关于带有名称和邮件范围的 ASAuthorizationAppleIDRequest 返回 nil 值的主要内容,如果未能解决你的问题,请参考以下文章

电子邮件数据库设计(模式)

Python 和 imaplib:无需下载完整电子邮件即可获取附件名称或正文

带有多个附件 + html 的 SMTP 邮件 Mime

附件名称和文件扩展名在电子邮件 *.eml 中不起作用

jquery正则表达式验证是否带有小数是否中文名称组成是否全由8位数字组成电话码格式邮件地址

PHP Pear Mail 无法发送带有 Unicode 字符的名称