iOS 接入 Google、Facebook 登录(一)

avatar
奇舞团移动端团队 @奇舞团

级别:★☆☆☆☆
标签:「iOS 接入 Google、Facebook 登录」「Firebase Google」「Firebase Facebook」
作者: WYW
审校: QiShare团队


前言

笔者最近调研了一下 iOS 接入Google、Facebook 登录,会整理2篇文章。并分享如下内容:

  1. 创建 Google、Facebook 应用;
  2. 接入 Google、Facebook 登录的过程;
  3. 接入 Google、Facebook 登录相关的 API;
  4. 接入 Google、Facebook 登录可能遇到的问题。

首先,笔者分享的是在Firebase 开放平台创建Google、Facebook 应用的内容。

一、在Firebase 开放平台创建Google应用

1. Firebase 介绍

Firebase 是Google 的移动平台,可帮助您快速开发优质应用并发展业务。

1.1 个人理解 Firebase

笔者在使用 Firebase 方面,目前调研过 Google、Facebook、Twitter、Github、匿名登录。Firebase 可以帮助开发者快速集成 Google、Facebook、Twitter登录等功能。

像国内的友盟开放平台可帮助开发者快速接入国内常用的三方登录分享等。Firebase 可帮助开发者快速接入国外常用三方登录分享等。

另外在授权登录方面 ,Firebase 对接入的三方做了进一步的封装。开发者(服务端)在接入三方时,可选择不直接和三方返回的授权信息交互,而是通过三方返回的授权信息,调用 Firebase 相关API 获取 Firebase 开放平台返回的token信息(jwt格式)进行交互。这样开发者(服务端)可只与 Firebase 平台返回的 token信息进行交互验证三方用户授权信息有效性即可。

注意:因为墙的原因,Google、Facebook 登录需要在科学上网的环境下才能正常使用。

下边笔者演示一下使用 Firebase 接入 Google、Facebook 登录后,进行授权的流程,示意图如下,点击 Google 登录按钮后,获取到了用户的 Google 信息,然后使用相关信息进一步获取到 Firebase 开放平台对应的 Google 信息。Facebook 登录流程类似。

1.2 笔者使用 Firebase 接入 Google、Facebook 登录的效果图如下。

Google、Facebook 登录效果图.gif

下一部分,笔者会分享下在 Firebase 开放平台创建应用及在 Firebase 开放平台启用相应登录的过程。

2.在 Firebase 开放平台添加项目并创建应用

2.1 打开 Firebase 开放平台,可使用 Google 账号登录
2.2 添加项目
2.3 点击创建好的项目并且选择添加相应的 iOS、Android 应用
2.4 填写要求的应用信息如 BundleID、应用名、AppStoreID(选填),最后下载配置文件 GoogleService-Info.plist
2.5 我们后续的 Google 登录主要会用到配置文件中的 CLIENT_ID、REVERSED_CLIENT_ID 的值
2.6 启用 Google、Facebook 登录

打开我们创建好的应用,依次查看 Authtication-> 登录方法 -> 启用 Google、Facebook 登录

注意:(1)在使用 Firebase 接入 Facebook 登录的情况下,复制出来启用 Facebook 时显示的 OAuth 重定向 URI ,这个重定向 URI 我们会粘贴到在Facebook 平台的创建的应用的有效 OAuth 跳转 URI 处。

OAuth 跳转 URL 的复制如下图所示

FirebaseFacebookMatchAuth.png

注意:(2)上图中的 应用 ID 和 应用密钥的填写内容要和 Facebook 开放平台创建的应用生成的应用编号及应用密钥保持一致,否则当用户使用 Facebook 授权登录后,使用 Firbase 相关 API 校验登录信息的时候会报错如下。

Error Domain=FIRAuthErrorDomain Code=17004 "Unsuccessful debug_token response from Facebook: {"error":{"message":"Error validating application. Invalid application ID.","type":"OAuthException","code":190,"fbtrace_id":"A-bczlMKMvzVfRTnOSP21_B"}}" UserInfo={NSLocalizedDescription=Unsuccessful debug_token response from Facebook: {"error":{"message":"Error validating application. Invalid application ID.","type":"OAuthException","code":190,"fbtrace_id":"A-bczlMKMvzVfRTnOSP21_B"}}, FIRAuthErrorUserInfoNameKey=ERROR_INVALID_CREDENTIAL}

下一部分,笔者会介绍下,在 Facebook 开放平台创建应用、添加测试用户、应用审核相关的内容。

二、 在 Facebook 开放平台创建应用

1. 在 Facebook 开放平台创建应用

1.1 打开 Facebook 开放平台,使用 Facebook 账号密码登录
1.2 添加新应用,依次填写信息
1.3 补充新建的应用的设置的基本信息
1.3.1 填写隐私政策网址、服务条款网址、iOS Bunde ID、Android 包名、包签名、类名等信息
1.4 在创建的 Facebook 应用的 产品 Facebook 登录 -> 设置中 填写有效的 OAuth 跳转 URI,这个 URI 在 本文 第一部分的2.6 中提到过。

2. 填写应用中的测试用户

2.1 在新建应用的用户身份的测试用户部分,添加测试用户
2.2 点击测试用户的右侧的编辑按钮,可以更改测试用户的账号和密码
2.3 测试用户的账号使用默认的邮箱即可,密码在 1.4.2 中指定

3. 应用审核

3.1 应用审核申请
3.2 应用审核 相关
3.3 点击应用审核的申请(这里应该会先操作应用编号后的开发中改为发布)
3.4 审核时间为2至3天

下一部分,笔者会分享下,使用 Firebase 接入 Google、Facebook 登录的过程。

三、 使用 Firebase 接入 Google、Facebook 登录

1. 集成方式

1.1 使用 Cocoapods 的方式接入 Google、Facebook 登录

2. Firebase 相关文档及集成过程

2.1 使用 FirebaseUI 轻松向 iOS 应用添加登录服务
2.2 笔者在项目的 Podfile 中填写的如下内容

大家可也以根据需要指定 Firebase 具体的版本号,笔者在调研过程中 FirebaseUI 最新可用版本为8.4.1

#	pod 'FirebaseUI/Auth'
	pod 'FirebaseUI/Google'
	pod 'FirebaseUI/Facebook'

也可以使用如下指定具体版本号的方式。

# pod 'FirebaseUI/Auth', '~> 8.4.1'
pod 'FirebaseUI/Google', '~> 8.4.1'
pod 'FirebaseUI/Facebook', '~> 8.4.1'

后来笔者再次查看文档,发现上述pod 内容可更改如下,并且下边的方式Pods的文件占内存最小:

pod 'Firebase/Auth', '~> 6.16.0'
pod 'GoogleSignIn', '~> 5.0.2'
pod 'FBSDKLoginKit', '~>6.0.0'

3. Firebase 接入 Google 登录具体操作

3.1 项目配置 Google 登录

为了正常使用Google登录需要

  1. Firebase 控制台中,打开 Authentication(身份验证)部分并启用 Google 登录服务。
  2. 在您的 Xcode 项目中,TARGETS-> Info -> URL Types -> URL Schemes将您的倒序客户端 ID 添加为网址架构。您可以在 GoogleService-Info.plist 文件中找到REVERSED_CLIENT_ID对应的值。

Google URL Types 配置示意图引自Firebase

3.2 把从 Firebase 开放平台下载的配置文件 GoogleService-Info.plist 拖拽到项目中
3.3 在应用启动的时,初始化 Firebase SDK
#import <Firebase.h>

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    

	// Firebase 初始化配置
	[FIRApp configure];
	[GIDSignIn sharedInstance].clientID = [FIRApp defaultApp].options.clientID;
    // 其他代码... 
    return YES;
}
3.4 Google 登录需要在如下代理方法中做处理
3.4.1 AppDelegate.m 文件中的代理方法
- (BOOL)application:(nonnull UIApplication *)application
            openURL:(nonnull NSURL *)url
            options:(nonnull NSDictionary<NSString *, id> *)options {
    
    return [[GIDSignIn sharedInstance] handleURL:url];
}

//  ios(4.2, 9.0)
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(nullable NSString *)sourceApplication annotation:(id)annotation {
    if ([url.absoluteString containsString:[FIRApp defaultApp].options.clientID]) {
        return [[GIDSignIn sharedInstance] handleURL:url];
    }
    return NO;
}
3.4.2 SceneDelegate.m 中的代理方法
- (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts  API_AVAILABLE(ios(13.0)){
    
    UIOpenURLContext *openURLContext = URLContexts.allObjects.firstObject;
    if ([openURLContext.URL.absoluteString containsString:[FIRApp defaultApp].options.clientID]) {
        [[GIDSignIn sharedInstance] handleURL:openURLContext.URL];
    }
}
3.5 Google 登录按钮相关代码及处理 Google 登录成功失败结果相关代码
#import <GoogleSignIn/GoogleSignIn.h>

// 遵守代理 <GIDSignInDelegate>

// 设置代理
[GIDSignIn sharedInstance].delegate = self;
// 必须设置 否则会Crash
[GIDSignIn sharedInstance].presentingViewController = self;

// Firebase 封装的 Google 登录按钮
GIDSignInButton *gidSignInBtn = [GIDSignInButton new];
gidSignInBtn.frame = CGRectMake(20.0, 120.0, 100.0, 40.0);
gidSignInBtn.center = self.view.center;
[self.view addSubview:gidSignInBtn];


// 实现代理方法
- (void)signIn:(GIDSignIn *)signIn didSignInForUser:(GIDGoogleUser *)user withError:(NSError *)error {
    
    if (!error) {
        NSLog(@"用户ID:%@", user.userID);
        
        GIDAuthentication *authentication = user.authentication;
        FIRAuthCredential *credential =
        [FIRGoogleAuthProvider credentialWithIDToken:authentication.idToken
                                         accessToken:authentication.accessToken];
        NSLog(@"credential Provider:%@", credential.provider);
        // Firebase 身份验证
        // Summary
        // Asynchronously signs in to Firebase with the given 3rd-party credentials (e.g. a Facebook login Access Token, a Google ID Token/Access Token pair, etc.) and returns additional identity provider data.
        // 三方异步登录Firebase
        [[FIRAuth auth] signInWithCredential:credential completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) {
            if (error) {
                NSLog(@"错误信息:%@", error.debugDescription);
            }
            if (!authResult) {
                NSLog(@"授权结果为空");
                return;
            }
            NSLog(@"Firebase uid:%@", authResult.user.uid);
            
            // 用于获取登录用户 Firebase token 信息交给服务端校验
            [[FIRAuth auth].currentUser getIDTokenWithCompletion:^(NSString * _Nullable token, NSError * _Nullable error) {
                if (error) {
                    
                    NSLog(@"获取当前token出现错误:%@", error);
                    return;
                }
                // Send token to your backend via HTTPS
                NSLog(@"Firebase当前用户 token 信息:%@", token);
           }];
            /**
             * 2020-03-06 20:48:46.859887+0800 FirebaseDemo[95438:3699395] credential Provider:google.com
             * 2020-03-06 20:48:47.914463+0800 FirebaseDemo[95438:3699395] Firebase uid:ma4dqHEO7JZm************QVE3
             * 2020-03-06 21:21:22.486530+0800 FirebaseDemo[95931:3798238] Firebase当前用户 token 信息:eyJhbGciOiJSUzI1NiIsImtpZCI6IjhjZjBjNjQyZDQ.*********4ZTRiZDc5OTkzOTZiNTY3NDAiLCJ0eX*********vbSJ9fQ.pvyaaG2dKKDH4CxO4VGiq_jcwDnmP************gQhHE-j-W
             // 这部分token 信息是 jwt 格式的内容
             */
        }];
    } else {
        NSLog(@"%@", error.debugDescription);
        self.userInfoLabel.text = error.debugDescription;
    }
}

在上述代码中,当我们点击的 Firebase 为我们提供好的 GIDSignInButton 的时候,便会执行 Google 登录的流程。Google 登录成功或失败的结果会在 - (void)signIn:(GIDSignIn *)signIn didSignInForUser:(GIDGoogleUser *)user withError:(NSError *)error 的代理方法中回调。

4. Firebase 接入 Facebook 登录具体操作

4.1 项目配置 Facebook 登录
4.1.1 配置 Facebook 的 AppID 、URL Scheme及 FacebookDisplayName

在 Info.plist 文件中配置如下FacebookAppID(CFBundleURLSchemes)及使用Facebook 授权的时候,显示的 Facebook 授权的应用名称(FacebookDisplayName对应的值控制)。

注意:

FacebookAppID 对应的值换成我们在 Facebook 平台创建的应用的 应用编号 ,如应用编号是12345678,那么 FacebookAppID 对应的值为12345678。

CFBundleURLSchemes 对应的值换成 fb 追加我们在 Facebook 平台创建的应用的 应用编号 ,如应用编号是12345678,那么 CFBundleURLSchemes 对应的值 fb12345678

<key>CFBundleURLTypes</key> <array> <dict> <key>CFBundleURLSchemes</key> <array> <string>fb8554384xxxxxxxx</string> </array> </dict> </array> <key>FacebookAppID</key> <string>8554384xxxxxxxx</string> <key>FacebookDisplayName</key> <string>FirebaseDemo</string>

如果没有填写 CFBundleURLSchemes 的值应用会 Crash,并报出如下问题。

2020-03-05 13:07:46.219473+0800 FirebaseDemo[11204:2947177] *** Terminating app due to uncaught exception 'InvalidOperationException', reason: 'fb8554384xxxxxxxx is not registered as a URL scheme. Please add it in your Info.plist'

4.1.2 配置 LSApplicationQueriesSchemes

在 Info.plist 文件中填写如下 Facebook 白名单,否则不能从应用中跳转至 Facebook 应用。不填写 Facebook 的白名单现象是,即便是手机端安装了 Facebook 应用,依然不会提示跳转至 Facebook 授权登录。而只会弹出网页授权登录。


<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>fbauth2</string> <string>fbshareextension</string> </array>

笔者在使用 Facebook时尝试过使用如下简短的白名单也是可以的。

<string>fbauth</string> <string>fbauth2</string> 
4.2 在应用启动的时,调用准备使用 Facebook SDK 的代码及初始化Facebook SDK 的代码
#import <FBSDKLoginKit/FBSDKLoginKit.h>

 // 为了使用 Facebook SDK 应该调用如下方法
[[FBSDKApplicationDelegate sharedInstance] application:application didFinishLaunchingWithOptions:launchOptions];
// 注册 FacebookAppID
[FBSDKSettings setAppID:kFacebookAppID];
4.3 Facebook 登录需要在如下代理方法中做处理

如下代理方法用于手机端安装了 Facebook 的情况下,从我们的应用跳转到 Facebook ,然后从 Facebook 跳转回我们的应用的时候,移除之前模态出的授权视图。

4.3.1 AppDelegate.m 文件中的代理方法

- (BOOL)application:(nonnull UIApplication *)application
            openURL:(nonnull NSURL *)url
            options:(nonnull NSDictionary<NSString *, id> *)options {
    
    if (@available(iOS 9.0, *)) {
        return [[FBSDKApplicationDelegate sharedInstance] application:application openURL:url sourceApplication:options[UIApplicationOpenURLOptionsSourceApplicationKey] annotation:options[UIApplicationOpenURLOptionsAnnotationKey]];
    } else {
        // Fallback on earlier versions
    }
}

//  ios(4.2, 9.0)
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(nullable NSString *)sourceApplication annotation:(id)annotation {
    if ([url.absoluteString containsString:kFacebookAppID]) {
        return [[FBSDKApplicationDelegate sharedInstance] application:application openURL:url sourceApplication:sourceApplication annotation:annotation];
    }
    return NO;
}
4.3.2 SceneDelegate.m 中的代理方法
- (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts  API_AVAILABLE(ios(13.0)){
    
    UIOpenURLContext *openURLContext = URLContexts.allObjects.firstObject;
    if (openURLContext) {
        if ([openURLContext.URL.absoluteString containsString:kFacebookAppID]) {
             [[FBSDKApplicationDelegate sharedInstance] application:UIApplication.sharedApplication openURL:openURLContext.URL sourceApplication:openURLContext.options.sourceApplication annotation:openURLContext.options.annotation];
            return;
        }
    }
}
4.4 Facebook 登录按钮相关代码及处理 Facebook 登录成功失败结果相关代码
#import "FBSDKLoginKit.h"

// 遵守代理 <FBSDKLoginButtonDelegate>

// Firebase 封装的Facebook 登录按钮
FBSDKLoginButton *fbLoginBtn = [FBSDKLoginButton new];
fbLoginBtn.frame = CGRectMake(20.0, 100.0, 120.0, 40.0);
fbLoginBtn.center = self.view.center;
fbLoginBtn.delegate = self;
[self.view addSubview:fbLoginBtn];

// FBSDKLoginButtonDelegate 代理方法
- (void)loginButton:(FBSDKLoginButton *)loginButton didCompleteWithResult:(FBSDKLoginManagerLoginResult *)result error:(NSError *)error {
    
    if (error) {
        NSLog(@"错误信息:%@", error);
    } else {
        FIRAuthCredential *credential =
        [FIRFacebookAuthProvider credentialWithAccessToken:result.token.tokenString];
        NSLog(@"credential Provider:%@", credential.provider);
        // Firebase 身份验证
        // Summary
        // Asynchronously signs in to Firebase with the given 3rd-party credentials (e.g. a Facebook login Access Token, a Google ID Token/Access Token pair, etc.) and returns additional identity provider data.
        // 三方异步登录Firebase
        [[FIRAuth auth] signInWithCredential:credential completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) {
            if (error) {
                NSLog(@"错误信息:%@", error.debugDescription);
            }
            if (!authResult) {
                NSLog(@"授权结果为空");
                return;
            }
            NSLog(@"Firebase uid:%@", authResult.user.uid);
            
            
            
            /**
             * 2020-03-06 20:35:42.995671+0800 FirebaseDemo[95438:3699395] token信息:<FBSDKAccessToken: 0x600001bf6580>
             * 2020-03-06 20:35:45.301482+0800 FirebaseDemo[95438:3699395] Firebase uid:X8U372A8****************s3s1
             * 2020-03-06 21:22:49.582470+0800 FirebaseDemo[95931:3798238] Firebase当前用户 token 信息:eyJhbGc*******************************************************AiLCJ0eXAiOiJKV1QifQ.eyJuYW1lIjoi546L5rC45pe6IiwicGljdHVyZSI6Imh0dHBzOi8vZ3JhcGguZmFjZWJvb2suY29tLzEwMTAyNzQ5NDgxOTk4My9wa*******************************************************2tlbi5nb29nbGUuY29tL2Zpci1kZW1vLThkZj*******************************************************DM1MDA5NjgsInVzZXJfaWQiOiJYOFUzNzJBOG*******************************************************nlCdzNnS01nMXZ6czNzMSIsImlhdCI6MTU4MzUwMDk2OCwiZXhwIjoxNTgzNTA0NTY4LCJmaXJlYmFzZSI6eyJpZGVudGl0aWVzIjp7ImZhY2Vib29rLmNvbSI6WyIxMDEwMjc0OTQ4MTk5ODMiXX0sInNpZ25faW5fcHJvdmlkZXIiOiJmYWNlYm9vay5jb20ifX0.VuhMUV_hr9Bc0Alrv2MS1X*******************************************************omeMXd5ebEe_FtKXEvSppDV8TN66p-*******************************************************lZpe-*******************************************************f-fyZ0lEK-p0PWB96WMKKY7jeVvPo_LR89u88kvjf7C-*******************************************************TWJmEYCMLqqtw9A
             */
            // 这部分token 信息是 jwt 格式的内容
        }];
        NSLog(@"token信息:%@", result.token);
        self.userInfoLabel.text = [NSString stringWithFormat:@"token信息:%@", result.token.tokenString];
        
    }
}

// 当点击 Facebook Log out 按钮的时候会调用这个代理方法
- (void)loginButtonDidLogOut:(FBSDKLoginButton *)loginButton {
    
    NSLog(@"退出登录");
}

目前,我们已经在三方登录授权成功后,获取到三方 App 返回 token 信息,并获取到了 Firebase token 信息。下边我们就可以使用相关 Firebase token 信息,到服务端去验证相关信息的有效性了。

四、程序化身份验证

笔者对服务端的开发不大了解,笔者看到了这个网址

验证 ID 令牌,可能是可以用于服务端校验客户端授权信息(客户端 点击 Google、Facebook 登录获取到的授权信息)有效性的文档。

五、小结

笔者在本文中记录了使用 Firebase 集成 Google、Facebook 登录的开放平台项目创建、项目相关配置过程及 Firebase 提供的 Google、Facebook 登录相关代码;不过可能我们考虑到不想增添无关代码及资源文件及因此带来的部分包体积的增加,或许考虑到想把 Google、Facebook的登录单独接入来稍微减少编译时间,此时我们也可以选择直接接入 Google、Facebook 提供的 SDK 。在下一篇文章中,笔者会分享直接接入 Google、Facebook 登录的 SDK 及相关代码。

参考学习网址


了解更多iOS及相关新技术,请关注我们的公众号:

小编微信:可加并拉入《QiShare技术交流群》。

关注我们的途径有:
QiShare(简书)
QiShare(掘金)
QiShare(知乎)
QiShare(GitHub)
QiShare(CocoaChina)
QiShare(StackOverflow)
QiShare(微信公众号)

推荐文章:
Nginx 入门实战
iOS中的3D变换(二)
iOS中的3D变换(一)
WebSocket 双端实践(iOS/ Golang)
今天我们来聊一聊WebSocket(iOS/Golang)
用 Swift 进行贝塞尔曲线绘制
Swift 5.1 (11) - 方法
Swift 5.1 (10) - 属性
iOS App后台保活
奇舞周刊