带有名称和邮件范围的 ASAuthorizationAppleIDRequest 返回 nil 值
Posted
技术标签:
【中文标题】带有名称和邮件范围的 ASAuthorizationAppleIDRequest 返回 nil 值【英文标题】:ASAuthorizationAppleIDRequest with name and mail scope returns nil values 【发布时间】:2019-12-26 19:37:45 【问题描述】:我正在实施 Sign in with Apple 并注意到返回的 ASAuthorizationAppleIDCredential
的 email
和 fullName
属性仅在此 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:无需下载完整电子邮件即可获取附件名称或正文