sign in with Apple和facebook登录集成

sign in with Apple 接入

1.项目配置

  • 需要配置具备sign in with Apple功能的profile文件
  • xcode项目的 signing and capabilities中需要新增sign in with apple模块

2.代码接入

1. 创建登录按钮

       // 使用系统提供的按钮,要注意不支持系统版本的处理
   if (@available(iOS 13.0, *)) {
       ASAuthorizationAppleIDButton *appleIDBtn = [ASAuthorizationAppleIDButton buttonWithType:ASAuthorizationAppleIDButtonTypeDefault style:ASAuthorizationAppleIDButtonStyleWhite];
       appleIDBtn.frame = CGRectMake(30, self.view.bounds.size.height - 180,   self.view.bounds.size.width - 60, 100);
       [appleIDBtn addTarget:self action:@selector(didAppleIDBtnClicked)       forControlEvents:UIControlEventTouchUpInside];
   }

这里的sign in with apple按钮有两种文本,三种样式:

   typedef NS_ENUM(NSInteger, ASAuthorizationAppleIDButtonType) {
   ASAuthorizationAppleIDButtonTypeSignIn,
   ASAuthorizationAppleIDButtonTypeContinue,

   ASAuthorizationAppleIDButtonTypeSignUp API_AVAILABLE(ios(13.2),     macos(10.15.1), tvos(13.1)) API_UNAVAILABLE(watchos),

   ASAuthorizationAppleIDButtonTypeDefault =   ASAuthorizationAppleIDButtonTypeSignIn,
   } NS_SWIFT_NAME(ASAuthorizationAppleIDButton.ButtonType)    API_AVAILABLE(ios(13.0), macos(10.15), tvos(13.0))  API_UNAVAILABLE(watchos);


   typedef NS_ENUM(NSInteger, ASAuthorizationAppleIDButtonStyle) {
   ASAuthorizationAppleIDButtonStyleWhite,
   ASAuthorizationAppleIDButtonStyleWhiteOutline,
   ASAuthorizationAppleIDButtonStyleBlack,
   } NS_SWIFT_NAME(ASAuthorizationAppleIDButton.Style)     API_AVAILABLE(ios(13.0), macos(10.15), tvos(13.0))  API_UNAVAILABLE(watchos);
   

2. 监听按钮点击事件,发送登录请求

if (@available(iOS 13.0, *)) {
        // 基于用户的Apple ID授权用户,生成用户授权请求的一种机制
        ASAuthorizationAppleIDProvider *appleIDProvider = [[ASAuthorizationAppleIDProvider alloc] init];
        // 创建新的AppleID 授权请求
        ASAuthorizationAppleIDRequest *appleIDRequest = [appleIDProvider createRequest];
        // 在用户授权期间请求的联系信息
        appleIDRequest.requestedScopes = @[ASAuthorizationScopeFullName, ASAuthorizationScopeEmail];
        // 由ASAuthorizationAppleIDProvider创建的授权请求 管理授权请求的控制器
        ASAuthorizationController *authorizationController = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[appleIDRequest]];
        // 设置授权控制器通知授权请求的成功与失败的代理
        authorizationController.delegate = self;
        // 设置提供 展示上下文的代理,在这个上下文中 系统可以展示授权界面给用户
        authorizationController.presentationContextProvider = self;
        // 开始登录请求
        [authorizationController performRequests];
    }else{
        // 处理不支持系统版本
        NSLog(@"该系统版本不可用Apple登录");
    }
// 如果存在iCloud Keychain 凭证或者AppleID 凭证提示用户
- (void)perfomExistingAccountSetupFlows{
    
    if (@available(iOS 13.0, *)) {
        // 基于用户的Apple ID授权用户,生成用户授权请求的一种机制
        ASAuthorizationAppleIDProvider *appleIDProvider = [[ASAuthorizationAppleIDProvider alloc] init];
        // 授权请求AppleID
        ASAuthorizationAppleIDRequest *appleIDRequest = [appleIDProvider createRequest];
        // 为了执行钥匙串凭证分享生成请求的一种机制
        ASAuthorizationPasswordProvider *passwordProvider = [[ASAuthorizationPasswordProvider alloc] init];
        ASAuthorizationPasswordRequest *passwordRequest = [passwordProvider createRequest];
        // 由ASAuthorizationAppleIDProvider创建的授权请求 管理授权请求的控制器
        ASAuthorizationController *authorizationController = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[appleIDRequest, passwordRequest]];
        // 设置授权控制器通知授权请求的成功与失败的代理
        authorizationController.delegate = self;
        // 设置提供 展示上下文的代理,在这个上下文中 系统可以展示授权界面给用户
        authorizationController.presentationContextProvider = self;
        // 在控制器初始化期间启动授权流
        [authorizationController performRequests];
    }else{
        // 处理不支持系统版本
        NSLog(@"该系统版本不可用Apple登录");
    }
}

3. 通过代理方法监听回调结果

  • 授权成功
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization API_AVAILABLE(ios(13.0)){
 
 if ([authorization.credential isKindOfClass:[ASAuthorizationAppleIDCredential class]]) {
     // 用户登录使用ASAuthorizationAppleIDCredential
     ASAuthorizationAppleIDCredential *appleIDCredential = authorization.credential;
     NSString *user = appleIDCredential.user;
     
     if (user) {
         [YostarKeychain save:KEYCHAIN_IDENTIFIER(@"userIdentifier") data:user];
     }
     // 使用过授权的,可能获取不到以下三个参数
     NSString *familyName = appleIDCredential.fullName.familyName;
     NSString *givenName = appleIDCredential.fullName.givenName;
     NSString *email = appleIDCredential.email;
     
     NSData *identityToken = appleIDCredential.identityToken;
     NSData *authorizationCode = appleIDCredential.authorizationCode;
     
     // 服务器验证需要使用的参数
     NSString *identityTokenStr = [[NSString alloc] initWithData:identityToken encoding:NSUTF8StringEncoding];
     NSString *authorizationCodeStr = [[NSString alloc] initWithData:authorizationCode encoding:NSUTF8StringEncoding];
     NSLog(@"%@\n\n%@", identityTokenStr, authorizationCodeStr);
     
     // Create an account in your system.
     // For the purpose of this demo app, store the userIdentifier in the keychain.
     
 }
}

sign in with Apple授权成功后,可以拿到以下数据:

 > 1.userId :用户的唯一标识符,一个apple ID对应一个唯一的userId,通过这个id可以  在后台用作数据绑定

> 2.Verification data:后台用来用作身份验证的数据,具体验证流程参考:https://www.yuque.com/zhanglong/bb0s5d/cxbh7n

> 3.用户名和邮箱数据
  • 授权失败
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)error API_AVAILABLE(ios(13.0)){
 // Handle error.
 NSLog(@"Handle error:%@", error);
 NSString *errorMsg = nil;
 switch (error.code) {
     case ASAuthorizationErrorCanceled:
         errorMsg = @"用户取消了授权请求";
         break;
     case ASAuthorizationErrorFailed:
         errorMsg = @"授权请求失败";
         break;
     case ASAuthorizationErrorInvalidResponse:
         errorMsg = @"授权请求响应无效";
         break;
     case ASAuthorizationErrorNotHandled:
         errorMsg = @"未能处理授权请求";
         break;
     case ASAuthorizationErrorUnknown:
         errorMsg = @"授权请求失败未知原因";
         break;
         
     default:
         break;
 }
}

4.当用户终止app使用sign in with apple功能或者在设置中注销apple ID时,需要做退出登录处理,此时需要对此行为做监听,具体是在进入应用和后台返回前台时,需要用到userID去查询此时账户的登录状态,具体代码如下:

- (void)appleLoginStatueChange{
    
    if (@available(iOS 13.0, *)) {
            // A mechanism for generating requests to authenticate users based on their Apple ID.
            // 基于用户的Apple ID 生成授权用户请求的机制
            ASAuthorizationAppleIDProvider *appleIDProvider = [ASAuthorizationAppleIDProvider new];
            // 注意 存储用户标识信息需要使用钥匙串来存储 这里笔者简单期间 使用NSUserDefaults 做的简单示例
            NSString *userIdentifier = [YostarKeychain load:KEYCHAIN_IDENTIFIER(@"userIdentifier")];
            NSLog(@"observeAuthticationState----:%@",userIdentifier);
            if (userIdentifier) {
                NSString* __block errorMsg = nil;
                //Returns the credential state for the given user in a completion handler.
                // 在回调中返回用户的授权状态
                [appleIDProvider getCredentialStateForUserID:userIdentifier completion:^(ASAuthorizationAppleIDProviderCredentialState credentialState, NSError * _Nullable error) {
                    switch (credentialState) {
                            // 苹果证书的授权状态
                        case ASAuthorizationAppleIDProviderCredentialRevoked:
          // 苹果授权凭证失效 -> 此时是用户在设置中终止了此App的sign in with Apple功能
                            errorMsg = @"苹果授权凭证失效";
                            // 需要重新登录
                            break;
                        case ASAuthorizationAppleIDProviderCredentialAuthorized:
                            // 苹果授权凭证状态良好
                            errorMsg = @"苹果授权凭证状态良好";
                            break;
                        case ASAuthorizationAppleIDProviderCredentialNotFound:
                            // 未发现苹果授权凭证 -> 此时是注销apple ID的时候
                            // 需要重新登录
                            errorMsg = @"未发现苹果授权凭证";
                            break;
                            // 可以引导用户重新登录
                        case ASAuthorizationAppleIDProviderCredentialTransferred:
                            errorMsg = @"苹果授权信息变动";
                            break;
                    }
                    dispatch_async(dispatch_get_main_queue(), ^{
                        NSLog(@"SignInWithApple授权状态变化情况");
                        NSLog(@"%@", errorMsg);
                    });
            }];
                
        }
    }
}

5.后台验证

**主要流程是客户端拿到userId, identityToken, authorizationCode后传给后台,后台拼接参数调用接口:https://appleid.apple.com/auth/token去苹果服务器请求认证。

1. 参数的生成

client_id: 为app的 bundle identifier

code:为手机端获取到的 authorizationCode 信息

grant_type:传入固定字符串 authorization_code

client_secret:需要自己我们通过私钥生成

2.client_secret的生成

1.登录开发者后台,选中keys,创建 privateKey,获取到 Key ID 和 私钥,创建完之后把私钥下载下来,并保存好,注意,私钥只能下载一次

2.拿到上面所有信息之后,可以通过如下代码生成 client_secret ,代码为 Ruby 代码,确保已安装ruby环境。

require "jwt"

key_file = "Path to the private key"
team_id = "Your Team ID"
client_id = "Your App Bundle ID"
key_id = "The Key ID of the private key"
validity_period = 180 # In days. Max 180 (6 months) according to Apple docs.

private_key = OpenSSL::PKey::EC.new IO.read key_file

token = JWT.encode(
  {
    iss: team_id,
    iat: Time.now.to_i,
    exp: Time.now.to_i + 86400 * validity_period,
    aud: "https://appleid.apple.com",
    sub: client_id
  },
  private_key,
  "ES256",
  header_fields=
  {
    kid: key_id 
  }
)
puts token

3.创建文件 secret_gen.rb ,把上面代码粘贴进去,执行 ruby secret_gen.rb 即可生成 client_secret
代码中这个 key_file 需要指定刚才下载的文件的地址

3.调用接口:https://appleid.apple.com/auth/token
返回数据如下:

{

"access_token": "一个token,此处省略",

"token_type": "Bearer",

"expires_in": 3600,

"refresh_token": "一个token,此处省略",

"id_token": "结果是JWT,字符串形式,此处省略"

}
服务器拿到相应结果,其中 id_token 也是 JWT 数据,decode 出 payload 部分如下

{
  "iss": "https://appleid.apple.com",
  "aud": "这个对应app的bundleid",
  "exp": 1567494694,
  "iat": 1567494094,
  "sub": "这个字段和手机端获取的user信息相同",
  "at_hash": "nRYP2wGXBGT0bIYWibx4Yg",
  "auth_time": 1567494094
}

其中 aud 部分与你的app的bundleID一致, sub 部分即与手机端获得的 user 一致,服务器端通过对比 sub 字段信息是否与手机端上传的 user 信息一致来确定是否成功登录.

更详细的接入信息请参考:

https://www.yuque.com/zhanglong/bb0s5d/cxbh7n

https://www.jianshu.com/p/e1284bd8c72a

facebook登录接入(SDK6.2.0)

1.首先进入facebook开发者官方网站

facebook开发者官网:https://developers.facebook.com/?no_redirect=1

并且成为其开发者。

2.创建应用,获取应用编号

3.进入facebook集成登录模块

facebook登录集成文档:https://developers.facebook.com/docs/facebook-login/ios?sdk=fbsdk

  • 1.选择需要应用
  • 2.选择集成方式,此处建议使用cocoapods方式集成,官方提供下载的SDK缺少Bolts库,会导致集成错误
  • 3.在 Facebook 注册和配置您的应用,配置bundle ID
  • 4.配置工程项目,在xocde项目的info.plist中配置:
<key>CFBundleURLTypes</key> 
<array> 
<dict> 
<key>CFBundleURLSchemes</key> 
<array> <string>fb2516056732057358</string> </array> 
</dict> 
</array> 
<key>FacebookAppID</key> 
<string>2516056732057358</string> 
<key>FacebookDisplayName</key> 
<string>changeDemo</string>
 <key>LSApplicationQueriesSchemes</key> 
 <array>
 <string>fbapi</string> 
 <string>fbapi20130214</string>
 <string>fbapi20130410</string>
 <string>fbapi20130702</string> 
 <string>fbapi20131010</string> 
 <string>fbapi20131219</string> 
 <string>fbapi20140410</string> 
 <string>fbapi20140116</string> 
 <string>fbapi20150313</string> 
 <string>fbapi20150629</string> 
 <string>fbapi20160328</string>
 <string>fbauth</string> 
 <string>fb-messenger-share-api</string>
 <string>fbauth2</string> 
 <string>fbshareextension</string>
 </array>
  • 5.在appdelegate.m文件中导入:#import < FBSDKCoreKit/FBSDKCoreKit.h>,此时不考虑SceneDelegate存在的情况.
 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 
    [[ApplicationDelegate sharedInstance] application:application didFinishLaunchingWithOptions:launchOptions]; 
    // Add any custom logic here. 
    return YES;
 } 
 
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
     BOOL handled = [[ApplicationDelegate sharedInstance] application:application openURL:url sourceApplication:options[UIApplicationOpenURLOptionsSourceApplicationKey] annotation:options[UIApplicationOpenURLOptionsAnnotationKey] ]; 
     // Add any custom logic here. 
     return handled;
}

如果应用中存在SceneDelegate,那么还需配置一下代码:

- (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts { 
    UIOpenURLContext *openURLContext = URLContexts.allObjects.firstObject; 
    if (openURLContext) { 
         [[ApplicationDelegate sharedInstance] application:UIApplication.sharedApplication  openURL:openURLContext.URL sourceApplication:openURLContext.options.sourceApplication annotation:openURLContext.options.annotation]; 
    } 
    // Add any custom logic here. 
}
    
  • 6.将facebook登录代码接入到app中:

    • 使用facebook定制化FBSDKLoginButton按钮登录,此按钮会根据用户登录和登出自动更换文本样式
     FBSDKLoginButton *loginButton = [[FBSDKLoginButton alloc] init];
         // Optional: Place the button in the center of your view.
      loginButton.center = self.view.center;
     loginButton.permissions = @[@"public_profile", @"email"];
     loginButton.delegate = self;
         [self.view addSubview:loginButton];
    
     通过FBSDKLoginButtonDelegate代理监听登录的状态:
    
    - (void)loginButton:(FBSDKLoginButton *)loginButton
    didCompleteWithResult:(nullable FBSDKLoginManagerLoginResult *)result
              error:(nullable NSError *)error{
         if (result.token) {
                NSLog(@"登录成功,userID = %@",result.token.userID);
        }else{
                NSLog(@"登录失败");
            }
    }
    
    - (void)loginButtonDidLogOut:(FBSDKLoginButton *)loginButton{
            NSLog(@"---退出登录");
    }
    
    • 使用FBSDKLoginManager登录
    [self.loginManager logInWithPermissions:@[@"public_profile", @"email"]  fromViewController:self handler:^(FBSDKLoginManagerLoginResult * _Nullable result,  NSError * _Nullable error) {
        if (result.token) {
                NSLog(@"登录成功,userID = %@",result.token.userID);
        }else{
                 NSLog(@"登录失败");
        }
    }];
    
        // 退出登录
        [self.loginManager logOut];
    
    • 判断当前用户是否登录:
    if ([FBSDKAccessToken currentAccessToken]) {
     // User is logged in, do work such as go to next view controller. 
    }
    

facebook登录成功,最终会获取到FBSDKAccessToken类型的模型,如下:

@property (class, nonatomic, copy, nullable) FBSDKAccessToken *currentAccessToken;

@property (nonatomic, copy, readonly) NSDate *expirationDate;

@property (nonatomic, copy, readonly) NSDate *refreshDate;

// 身份验证的令牌
@property (nonatomic, copy, readonly) NSString *tokenString;

// 用户唯一id
@property (nonatomic, copy, readonly) NSString *userID;

这里我们需要传给后台的就是,userID和tokenString

经测试发现最新的facebook不支持facebook App授权登录,仅支持应用内跳转到网页登录

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,039评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,426评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,417评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,868评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,892评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,692评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,416评论 3 419
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,326评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,782评论 1 316
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,957评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,102评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,790评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,442评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,996评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,113评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,332评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,044评论 2 355

推荐阅读更多精彩内容