苹果登录:只支持iOS13及以上
wwdc2019介绍苹果登录视频
https://developer.apple.com/videos/play/wwdc2019/706/
步骤:
----------第一步
第一步:导入头文件:#import <AuthenticationServices/AuthenticationServices.h>
----------第二步
第二步:创建一个授权苹果登录的按钮,这个按钮可以自定义;当然你也可以自定义苹果登录按钮的样式,
样式要求详见这个文档:Human Interface Guidelines
// Add “Sign In with Apple” button to your login view
func setupProviderLoginView() {
let authorizationButton = ASAuthorizationAppleIDButton()
authorizationButton.addTarget(self, action: #selector(handleAuthorizationAppleIDButtonPress), for: .touchUpInside)
self.loginProviderStackView.addArrangedSubview(authorizationButton)
}
// 或者oc
+ (UIView *) creatAppleIDAuthorizedButtonWithTarget:(id)target selector:(SEL)selector {
ASAuthorizationAppleIDButton *button = [ASAuthorizationAppleIDButton buttonWithType:(ASAuthorizationAppleIDButtonTypeSignIn) style:(ASAuthorizationAppleIDButtonStyleBlack)];
[button addTarget:target action:selector forControlEvents:(UIControlEventTouchUpInside)];
return button;
}
----------第三步
第三步:发起授权
- 在上述按钮点击事件 handleAuthorizationAppleIDButtonPress 中配置需要获取的数据权限范围
(例如:用户名、邮箱等),然后设置回调代理,并发起登录授权请求,代码如下: - 在该页面,用户可以选择是否给你返回他的名字和真实邮箱,当然用户也可以选择隐藏自己的邮箱,
此时开发者会得到一个虚拟的用户邮箱地址(此邮箱收到的邮件会转发到用户真实的邮箱上)。
当用户点击 “Continue” 按钮后,会进行 “Touch ID” 或者 “Face ID” 双重验证,然后回调数据给开发者。
// Configure request, setup delegates and perform authorization request
@objc func handleAuthorizationAppleIDButtonPress() {
let appleIDProvider = ASAuthorizationAppleIDProvider()
let request = appleIDProvider.createRequest()
request.requestedScopes = [.fullName, .email]
let authorizationController = ASAuthorizationController(authorizationRequests: [request])
authorizationController.delegate = self
authorizationController.presentationContextProvider = self
authorizationController.performRequests()
}
// objc方法
- (void) loginWithCompleteHandler:(LQAppleLoginCompleteHandler)completeHandler {
self.completeHander = completeHandler;
ASAuthorizationAppleIDProvider *provider = [[ASAuthorizationAppleIDProvider alloc]init];
ASAuthorizationAppleIDRequest *req = [provider createRequest];
req.requestedScopes = @[ASAuthorizationScopeFullName, ASAuthorizationScopeEmail];
ASAuthorizationController *controller = [[ASAuthorizationController alloc]initWithAuthorizationRequests:@[req]];
controller.delegate = self;
controller.presentationContextProvider = self;
[controller performRequests];
}
----------第四步
第四步:处理授权回调
-
我们需要在代码中实现两个代理回调 ASAuthorizationControllerDelegate、
ASAuthorizationControllerPresentationContextProviding 分别用于处理授权登录成功和失败、
以及提供用于展示授权页面的 Window,代码如下: -
在授权成功回调中,我们可以拿到以下几类数据:
- User ID: Unique, stable, team-scoped user ID,苹果用户唯一标识符,
该值在同一个开发者账号下的所有 App 下是一样的,开发者可以用该唯一标识符与自己后台系统的账号体系绑定起来
(这与国内的微信、QQ、微博等第三方登录流程基本一致)。 - Verification data: Identity token, code,验证数据,用于传给开发者后台服务器,
然后开发者服务器再向苹果的身份验证服务端验证本次授权登录请求数据的有效性和真实性,
详见 Sign In with Apple REST API。如果验证成功,可以根据 userIdentifier 判断账号是否已存在,
若存在,则返回自己账号系统的登录态,若不存在,则创建一个新的账号,并返回对应的登录态给 App。 - Account information: Name, verified email,苹果用户信息,包括全名、邮箱等。
- Real user indicator: High confidence indicator that likely real user,
用于判断当前登录的苹果账号是否是一个真实用户,取值有:unsupported、unknown、likelyReal。
- User ID: Unique, stable, team-scoped user ID,苹果用户唯一标识符,
/// MARK: ASAuthorizationControllerDelegate
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
let userIdentifier = appleIDCredential.user
let fullName = appleIDCredential.fullName
let email = appleIDCredential.email
let realUserStatus = credential.realUserStatus
let identityToken = credential.identityToken
let authCode = credential.authorizationCode
// Create account in your system
}
}
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
// Handle error.
}
/// MARK: ASAuthorizationControllerPresentationContextProviding
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
return self.view.window!
}
// oc授权成功的回调
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization {
if ([authorization.credential isKindOfClass:[ASAuthorizationAppleIDCredential class]]) {
ASAuthorizationAppleIDCredential *credential = authorization.credential;
NSString *user = credential.user;
NSString *familyName = credential.fullName.familyName;
NSString * givenName = credential.fullName.givenName;
NSString *email = credential.email;
NSData *identityToken = credential.identityToken;
NSData *code = credential.authorizationCode;
if (self.completeHander) {
self.completeHander(YES, user, familyName, givenName, email, nil, identityToken, code, nil, @"授权成功");
}
} else if ([authorization.credential isKindOfClass:[ASPasswordCredential class]]) {
// 使用现有的密码凭证登录
ASPasswordCredential *credential = authorization.credential;
// 用户唯一标识符
NSString *user = credential.user;
NSString *password = credential.password;
if (self.completeHander) {
self.completeHander(YES, user, nil, nil, nil, password, nil, nil, nil, @"授权成功");
}
}
}
// oc授权失败的回调
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)error {
NSString *msg = @"未知";
switch (error.code) {
case ASAuthorizationErrorCanceled:
msg = @"用户取消";
break;
case ASAuthorizationErrorFailed:
msg = @"授权请求失败";
break;
case ASAuthorizationErrorInvalidResponse:
msg = @"授权请求无响应";
break;
case ASAuthorizationErrorNotHandled:
msg = @"授权请求未处理";
break;
case ASAuthorizationErrorUnknown:
msg = @"授权失败,原因未知";
break;
default:
break;
}
if (self.completeHander) {
self.completeHander(NO, nil, nil, nil, nil, nil, nil, nil, error, msg);
}
}
// oc展示window
- (ASPresentationAnchor)presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller {
return [UIApplication sharedApplication].windows.firstObject;
}
-----------------------------------------------以上是一个完整的流程
-----------------------------------------------以下是异常处理流程
-----------------异常1
-
异常1:处理苹果账号变化的情况
-
当开发者的 App 通过苹果账号登录后,iOS/macOS 设备上登录的 Apple ID 发生变化时,例如:
- 设备上的 Apple ID 退出登录、切换新的账号登录;
- 用户在设置页面禁止 App 使用苹果账号登录,这个类似开启和关闭app的通知或者定位等权限
-
监听苹果账号变化的方法
- 我们可以 App 启动时,调用 ASAuthorizationAppleIDProvider 的 getCredentialState 方法,
传入当前用户的 UserIdentifier 进行判断:
- 我们可以 App 启动时,调用 ASAuthorizationAppleIDProvider 的 getCredentialState 方法,
-
苹果称,该 API 的速度非常快,我们可以在 App 每次启动时调用,然后根据结果做相应的处理:
- authorized:登录状态有效;
- revoked:上次使用苹果账号登录的凭据已被移除,需退出解除绑定并重新引导使用苹果登录;
- notFound:未登录,直接显示开发者 App 的登录页面。
let appleIDProvider = ASAuthorizationAppleIDProvider()
appleIDProvider.getCredentialState(forUserID: "currentUserIdentifier") { (credentialState, error) in
switch credentialState {
case .authorized:
// The Apple ID credential is valid
break
case .revoked:
// Apple ID Credential revoked, handle unlink
break
case .notFound:
// Credential not found, show login UI
break
default:
break
}
}
// 或者用通知监听
// Register for revocation notification
let center = NotificationCenter.default
let name = NSNotification.Name.ASAuthorizationAppleIDProviderCredentialRevoked
let observer = center.addObserver(forName: name, object: nil, queue: nil) { (Notification) in
// Sign the user out, optionally guide them to sign in again
}
// objc方
+ (void) checkAuthorizationStateWithUser:(NSString *) user
completeHandler:(void(^)(BOOL authorized, NSString *msg)) completeHandler {
if (user == nil || user.length <= 0) {
if (completeHandler) {
completeHandler(NO, @"用户标识符错误");
}
return;
}
ASAuthorizationAppleIDProvider *provider = [[ASAuthorizationAppleIDProvider alloc]init];
[provider getCredentialStateForUserID:user completion:^(ASAuthorizationAppleIDProviderCredentialState credentialState, NSError * _Nullable error) {
NSString *msg = @"未知";
BOOL authorized = NO;
switch (credentialState) {
case ASAuthorizationAppleIDProviderCredentialRevoked:
msg = @"授权被撤销";
authorized = NO;
break;
case ASAuthorizationAppleIDProviderCredentialAuthorized:
msg = @"已授权";
authorized = YES;
break;
case ASAuthorizationAppleIDProviderCredentialNotFound:
msg = @"未查到授权信息";
authorized = NO;
break;
case ASAuthorizationAppleIDProviderCredentialTransferred:
msg = @"授权信息变动";
authorized = NO;
break;
default:
authorized = NO;
break;
}
if (completeHandler) {
completeHandler(authorized, msg);
}
}];
}
// oc用通知监听苹果账号
- (void) startAppleIDObserverWithCompleteHandler:(LQAppleLoginObserverHandler) handler {
self.observerHander = handler;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(lq_signWithAppleIDStateChanged:) name:ASAuthorizationAppleIDProviderCredentialRevokedNotification object:nil];
}
- (void) lq_signWithAppleIDStateChanged:(NSNotification *) noti {
if (noti.name == ASAuthorizationAppleIDProviderCredentialRevokedNotification) {
if (self.observerHander) {
self.observerHander();
}
}
}
-----------------异常2
异常2:快速登录已有账户
-
情况1:如果一个 Apple ID 之前已在 App 中授权登录过了,此时点击 “Sign In with Apple” 会直接显示如下页面:
-
情况2:或者 App 上次用自己的账号/密码登录后保存在 Keychain 中,此时可以在登录页面直接请求读取密码进行自动填充:
-
对于读取 Keychain 中保存的密码,苹果提供了与授权登录类似的 API,我们可以在登录页面的 viewDidAppear 方法中执行代码如下:
/// 在登录界面viewDidAppear检查是否存在账号,如果存在,直接提示它是否用苹果账号登录,
/// 也可以等用户点击了之后再调用下面的方法也行,自行决定
/// Prompts the user if an existing iCloud Keychain credential or Apple ID credential is found.
func performExistingAccountSetupFlows() {
// Prepare requests for both Apple ID and password providers.
let requests = [ASAuthorizationAppleIDProvider().createRequest(),
ASAuthorizationPasswordProvider().createRequest()]
// Create an authorization controller with the given requests.
let authorizationController = ASAuthorizationController(authorizationRequests: requests)
authorizationController.delegate = self
authorizationController.presentationContextProvider = self
authorizationController.performRequests()
}
// 然后在回调方法中进行判断
func authorizationController(controller _: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
switch authorization.credential {
case let credential as ASAuthorizationAppleIDCredential:
let userIdentifier = credential.user
// Sign the user in using the Apple ID credential
case let credential as ASPasswordCredential:
// Sign the user in using their existing password credential
default: break
}
}
// oc方法
- (void) loginWithExistingAccount:(LQAppleLoginCompleteHandler)completeHandler {
self.completeHander = completeHandler;
ASAuthorizationAppleIDProvider *provider = [[ASAuthorizationAppleIDProvider alloc]init];
ASAuthorizationAppleIDRequest *req = [provider createRequest];
ASAuthorizationPasswordProvider *pasProvider = [[ASAuthorizationPasswordProvider alloc]init];
ASAuthorizationPasswordRequest *pasReq = [pasProvider createRequest];
NSMutableArray *arr = [NSMutableArray arrayWithCapacity:2];
if (req) {
[arr addObject:req];
}
if (pasReq) {
[arr addObject:pasReq];
}
ASAuthorizationController *controller = [[ASAuthorizationController alloc]initWithAuthorizationRequests:arr.copy];
controller.delegate = self;
controller.presentationContextProvider = self;
[controller performRequests];
}
-----------------异常3
- 异常3:Error Domain=com.apple.AuthenticationServices.AuthorizationError Code=1000
问题分析:这个问题是调用方法错误的问题,排除了xx.entitlements的问题
-----------------异常4
- 异常4:java后台怎么对接
https://blog.csdn.net/wth905541529/article/details/103312174 - 流程:
1.app获得到user参数和identityToken参数
2.后台只需要这两个参数,就传过去,identityToken在app是NSData类型,转换成NSString类型传给后台
-----------------异常5
- 异常5:php后台怎么对接
https://blog.csdn.net/u013862108/article/details/102890808
https://github.com/GriffinLedingham/php-apple-signin
-----------------异常6
- 异常6:查看哪些app使用了apple id登录
解决:到设置----Apple id------密码与安全性-------使用您apple id的app
问题1:怎么解决苹果账号与用户手机号码绑定的问题?
问题2:如果刚开始用户用了手机上的苹果账号登录,后面没有退出过app,但是在手机上退出了之前登录的苹果账号,
这时app应该怎么处理?