[iOS 13] Sign in with Apple 蘋果登錄

1. 啟用 Sign in with Apple

選擇項(xiàng)目 TARGETS -> Signing&Capabilities 晰韵,單擊下圖中的 3:Capability:


在彈出框中搜索找到 Sign in with Apple:


然后,雙擊,即可啟用Apple 登錄:

點(diǎn)擊右側(cè)的x验夯,可以關(guān)閉 Apple 登錄哟沫。

最后饥追,登錄開發(fā)者中心哨苛,在需要啟用 Sign in with Apple 的 Apple ID中勾選 Sign in with Apple:


這些完成后幢码,我們就可以在項(xiàng)目中使用相關(guān)的功能了。

2. 添加代碼

在需要使用 login with Apple 的地方奏甫,引入頭文件:

#import <AuthenticationServices/AuthenticationServices.h>

首先戈轿,我們需要一個登錄按鈕,系統(tǒng)為我們預(yù)設(shè)了一個固定樣式登錄按鈕ASAuthorizationAppleIDButton阵子,我們可以這樣使用它:

ASAuthorizationAppleIDButton *button = [ASAuthorizationAppleIDButton buttonWithType:(ASAuthorizationAppleIDButtonTypeSignIn) style:(ASAuthorizationAppleIDButtonStyleBlack)];
    [button addTarget:self action:@selector(buttonAction:) forControlEvents:(UIControlEventTouchUpInside)];
    
    button.frame = CGRectMake(60, 60, 200, 60);
    [self.view addSubview:button];

這時思杯,按鈕的樣式是這樣的:


如果我們設(shè)置的按鈕是正方形,或者寬度不足以顯示文字挠进,將會出現(xiàn)一個只有蘋果logo的按鈕:

通過參數(shù) type色乾、style可以設(shè)置為不同樣式的按鈕;當(dāng)然领突,我們也可以自定義暖璧,但是要遵循蘋果的相關(guān)設(shè)計規(guī)范,詳見 Human Interface Guidelines君旦。

接下來就是需要添加授權(quán)的相關(guān)代碼了澎办;

3. 申請授權(quán)

在申請授權(quán)的過程中使用到的類都比較簡單:

//主要作用是用創(chuàng)建相應(yīng)的請求,查詢用戶授權(quán)狀態(tài)
ASAuthorizationAppleIDProvider

// 授權(quán)請求金砍,可以設(shè)置具體的請求信息
ASAuthorizationAppleIDRequest

// 發(fā)送請求控制器局蚀,可以設(shè)置相應(yīng)的協(xié)議
ASAuthorizationController

這其中使用到了兩個協(xié)議: ASAuthorizationControllerDelegate
ASAuthorizationControllerPresentationContextProviding,前者用于接收請求成功或者失敗的回調(diào)捞魁;后者用于返回彈出請求框的window至会,一般返回當(dāng)前視圖window即可!

// ASAuthorizationControllerDelegate
// 成功的回調(diào)
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization NS_SWIFT_NAME(authorizationController(controller:didCompleteWithAuthorization:));
// 失敗的回調(diào)
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)error  NS_SWIFT_NAME(authorizationController(controller:didCompleteWithError:));

// ASAuthorizationControllerPresentationContextProviding
// 返回彈出請求視圖的window谱俭;
// ASPresentationAnchor 為 UIWindow 的別名:
// typedef UIWindow * ASPresentationAnchor;
- (ASPresentationAnchor)presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller;

4. 發(fā)起授權(quán)
//基于用戶的Apple ID授權(quán)用戶奉件,生成用戶授權(quán)請求的一種機(jī)制
ASAuthorizationAppleIDProvider *provider = [[ASAuthorizationAppleIDProvider alloc]init];
    // 創(chuàng)建請求
    ASAuthorizationAppleIDRequest *req = [provider createRequest];
// 設(shè)置請求的信息
    req.requestedScopes = @[ASAuthorizationScopeFullName, ASAuthorizationScopeEmail];
    // 創(chuàng)建管理授權(quán)請求的控制器
    ASAuthorizationController *controller = [[ASAuthorizationController alloc]initWithAuthorizationRequests:@[req]];
// 設(shè)置代理
    controller.delegate = self;
    controller.presentationContextProvider = self;
    // 發(fā)起請求
    [controller performRequests];

然后實(shí)現(xiàn)相應(yīng)的代理協(xié)議:

// 授權(quán)失敗的回調(diào)
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)error {

    NSString *msg = @"未知";
    
    switch (error.code) {
        case ASAuthorizationErrorCanceled:
            msg = @"用戶取消";
            break;
        case ASAuthorizationErrorFailed:
            msg = @"授權(quán)請求失敗";
            break;
        case ASAuthorizationErrorInvalidResponse:
            msg = @"授權(quán)請求無響應(yīng)";
            break;
        case ASAuthorizationErrorNotHandled:
            msg = @"授權(quán)請求未處理";
            break;
        case ASAuthorizationErrorUnknown:
            msg = @"授權(quán)失敗,原因未知";
            break;
            
        default:
            break;
    }
    
    NSLog(@"%@", msg);
}

// 授權(quán)成功的回調(diào)
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization {
    
    if ([authorization.credential isKindOfClass:[ASAuthorizationAppleIDCredential class]]) {
        ASAuthorizationAppleIDCredential *credential = authorization.credential;
 //蘋果用戶唯一標(biāo)識符昆著,
// 該值在同一個開發(fā)者賬號下的所有 App 下是一樣的县貌,
//開發(fā)者可以用該唯一標(biāo)識符與自己后臺系統(tǒng)的賬號體系綁定起來。
        NSString *user = credential.user;
// 獲取用戶名
        NSString *familyName = credential.fullName.familyName;
        NSString * givenName = credential.fullName.givenName;
// 獲取郵箱
        NSString *email = credential.email;
        // 獲取驗(yàn)證信息
// 驗(yàn)證數(shù)據(jù)凑懂,用于傳給開發(fā)者后臺服務(wù)器煤痕,
// 然后開發(fā)者服務(wù)器再向蘋果的身份驗(yàn)證服務(wù)端驗(yàn)證本次授權(quán)登錄請求數(shù)據(jù)的有效性和真實(shí)性,
// 如果驗(yàn)證成功接谨,可以根據(jù) userIdentifier 判斷賬號是否已存在摆碉,
// 若存在,則返回自己賬號系統(tǒng)的登錄態(tài)脓豪,若不存在巷帝,則創(chuàng)建一個新的賬號,并返回對應(yīng)的登錄態(tài)給 App扫夜。
        NSData *identityToken = credential.identityToken;
        NSData *code = credential.authorizationCode;
        
        if (self.completeHander) {
            self.completeHander(YES, user, familyName, givenName, email, nil, identityToken, code, nil, @"授權(quán)成功");
        }
        
    } else if ([authorization.credential isKindOfClass:[ASPasswordCredential class]]) {
        // 使用現(xiàn)有的密碼憑證登錄
        ASPasswordCredential *credential = authorization.credential;
        
        // 用戶唯一標(biāo)識符
        NSString *user = credential.user;
        NSString *password = credential.password;
        
        if (self.completeHander) {
            self.completeHander(YES, user, nil, nil, nil, password, nil, nil, nil, @"授權(quán)成功");
        }
    }
}

- (ASPresentationAnchor)presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller {
    return [UIApplication sharedApplication].windows.firstObject;
}

點(diǎn)擊登錄后楞泼,如果是未授權(quán)會彈出如下彈框:


這里的郵件地址驰徊,用戶可以選擇共享或者隱藏,如果選擇了隱藏堕阔,開發(fā)者將會獲得一個蘋果自動生成的一個郵箱地址棍厂,而不是用戶的真實(shí)郵箱;成功后返回的信息如下:

user: 004673.27e48e27b0e5853a7c5d744d9a1c8725.0683
 familyName: 劉 
givenName: 流火緋瞳 
email: c96fqxirwu@privaterelay.appleid.com

這里我選擇了隱藏郵箱超陆,返回的是蘋果生成的一個郵箱地址牺弹;
我們可以將 user 信息保存到keychain中,

如果我們已經(jīng)授權(quán)登錄成功侥猬,再次登錄的時候例驹,就會顯示如下的頁面:


如果把登錄的信息保存到Keychain,我們可以使用下面的方式進(jìn)行讀取密碼登錄:

    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];

這時獲取到的信息將只有user:

user: 004673.27e48e27b0e5853a7c5d744d9a1c8725.0683
 familyName: (null) 
givenName: (null) 
email: (null)

app登錄成功后退唠,需要將獲取到的 identityTokencode等信息發(fā)送給后臺荤胁,然后由后臺調(diào)用 Apple 的后臺API瞧预,來驗(yàn)證用戶的真實(shí)性,從而完成驗(yàn)證仅政,詳情參考 Sign In With Apple 從登陸到服務(wù)器驗(yàn)證

5. 檢測登錄/授權(quán)狀態(tài)

登錄成功后垢油,用戶是可以隨時取消授權(quán)的,或者用戶將 AppleID退出了當(dāng)前設(shè)備圆丹,都需要重新獲忍渤睢;
我們可以在應(yīng)用啟動的時候使用下面的方法來檢測用戶狀態(tài):

// 使用 user 信息辫封,查詢當(dāng)前用戶的狀態(tài)
+ (void) checkAuthorizationStateWithUser:(NSString *) user
                         completeHandler:(void(^)(BOOL authorized, NSString *msg)) completeHandler {
    
    if (user == nil || user.length <= 0) {
        if (completeHandler) {
            completeHandler(NO, @"用戶標(biāo)識符錯誤");
        }
        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 = @"授權(quán)被撤銷";
                authorized = NO;
                break;
            case ASAuthorizationAppleIDProviderCredentialAuthorized:
                msg = @"已授權(quán)";
                authorized = YES;
                break;
            case ASAuthorizationAppleIDProviderCredentialNotFound:
                msg = @"未查到授權(quán)信息";
                authorized = NO;
                break;
            case ASAuthorizationAppleIDProviderCredentialTransferred:
                msg = @"授權(quán)信息變動";
                authorized = NO;
                break;
                
            default:
                authorized = NO;
                break;
        }
        
        if (completeHandler) {
            completeHandler(authorized, msg);
        }
    }];
}

如果app在運(yùn)行中硝枉,我們可以通過添加通知的方法來實(shí)時監(jiān)控:

- (void) startAppleIDObserverWithCompleteHandler {
    
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(lq_signWithAppleIDStateChanged:) name:ASAuthorizationAppleIDProviderCredentialRevokedNotification object:nil];
}

- (void) lq_signWithAppleIDStateChanged:(NSNotification *) noti {
    
    NSLog(@"%@", noti.name);
    NSLog(@"%@", noti.userInfo);
}

這里的 userInfo 信息一直都是 null,需要我們再次使用上面的方法去檢測授權(quán)狀態(tài)倦微,然后做出相應(yīng)的處理妻味。

6. 取消授權(quán)

對應(yīng)用授權(quán)登錄后,我們可以在設(shè)備中取消對某個app的授權(quán)欣福,操作方法如下:

設(shè)置 -> Apple ID -> 密碼與安全性 -> 使用您 Apple ID 的 App

打開后會顯示所有使用 Apple ID 進(jìn)行登錄的 App:

選擇需要取消的app责球,停止使用 Apple ID 即可!

7. 一些問題

Error Domain=com.apple.AuthenticationServices.AuthorizationError Code=1000 "

項(xiàng)目配置中未添加 Sign in with Apple,參考文章第一步

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末拓劝,一起剝皮案震驚了整個濱河市雏逾,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌郑临,老刑警劉巖栖博,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異牧抵,居然都是意外死亡笛匙,警方通過查閱死者的電腦和手機(jī)侨把,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來妹孙,“玉大人秋柄,你說我怎么就攤上這事〈勒” “怎么了骇笔?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長嚣崭。 經(jīng)常有香客問我笨触,道長,這世上最難降的妖魔是什么雹舀? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任芦劣,我火速辦了婚禮,結(jié)果婚禮上说榆,老公的妹妹穿的比我還像新娘虚吟。我一直安慰自己,他們只是感情好签财,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布串慰。 她就那樣靜靜地躺著,像睡著了一般唱蒸。 火紅的嫁衣襯著肌膚如雪邦鲫。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天神汹,我揣著相機(jī)與錄音庆捺,去河邊找鬼。 笑死慎冤,一個胖子當(dāng)著我的面吹牛疼燥,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蚁堤,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼醉者,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了披诗?” 一聲冷哼從身側(cè)響起撬即,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎呈队,沒想到半個月后剥槐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡宪摧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年粒竖,在試婚紗的時候發(fā)現(xiàn)自己被綠了颅崩。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡蕊苗,死狀恐怖沿后,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情朽砰,我是刑警寧澤尖滚,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站瞧柔,受9級特大地震影響漆弄,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜造锅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一撼唾、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧备绽,春花似錦券坞、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽宇驾。三九已至倍靡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間课舍,已是汗流浹背塌西。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留筝尾,地道東北人捡需。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像筹淫,于是被迫代替她去往敵國和親站辉。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內(nèi)容