iOS 接入 Google、Facebook 登录(一) - twototwoto/WYW_Blog GitHub Wiki
前言
笔者最近调研了一下 iOS 接入Google、Facebook 登录,会整理2篇文章。并分享如下内容:
- 创建 Google、Facebook 应用;
- 接入 Google、Facebook 登录的过程;
- 接入 Google、Facebook 登录相关的 API;
- 接入 Google、Facebook 登录可能遇到的问题。
首先,笔者分享的是在Firebase 开放平台创建Google、Facebook 应用的内容。
Firebase 是Google 的移动平台,可帮助您快速开发优质应用并发展业务。
笔者在使用 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 登录流程类似。
下一部分,笔者会分享下在 Firebase 开放平台创建应用及在 Firebase 开放平台启用相应登录的过程。
2.1 打开 Firebase 开放平台,可使用 Google 账号登录
打开我们创建好的应用,依次查看 Authtication-> 登录方法 -> 启用 Google、Facebook 登录
注意:(1)在使用 Firebase 接入 Facebook 登录的情况下,复制出来启用 Facebook 时显示的 OAuth 重定向 URI ,这个重定向 URI 我们会粘贴到在Facebook 平台的创建的应用的有效 OAuth 跳转 URI 处。
OAuth 跳转 URL 的复制如下图所示
注意:(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 开放平台创建应用、添加测试用户、应用审核相关的内容。
1.1 打开 Facebook 开放平台,使用 Facebook 账号密码登录
1.3 补充新建的应用的设置的基本信息
3.1 应用审核申请
3.2 应用审核 相关
下一部分,笔者会分享下,使用 Firebase 接入 Google、Facebook 登录的过程。
大家可也以根据需要指定 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'
为了正常使用Google登录需要
- 在 Firebase 控制台中,打开 Authentication(身份验证)部分并启用 Google 登录服务。
- 在您的 Xcode 项目中,TARGETS-> Info -> URL Types -> URL Schemes将您的倒序客户端 ID 添加为网址架构。您可以在
GoogleService-Info.plist
文件中找到REVERSED_CLIENT_ID对应的值。
#import <Firebase.h>
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Firebase 初始化配置
[FIRApp configure];
[GIDSignIn sharedInstance].clientID = [FIRApp defaultApp].options.clientID;
// 其他代码...
return YES;
}
- (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;
}
- (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];
}
}
#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
的代理方法中回调。
在 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'
在 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>
#import <FBSDKLoginKit/FBSDKLoginKit.h>
// 为了使用 Facebook SDK 应该调用如下方法
[[FBSDKApplicationDelegate sharedInstance] application:application didFinishLaunchingWithOptions:launchOptions];
// 注册 FacebookAppID
[FBSDKSettings setAppID:kFacebookAppID];
如下代理方法用于手机端安装了 Facebook 的情况下,从我们的应用跳转到 Facebook ,然后从 Facebook 跳转回我们的应用的时候,移除之前模态出的授权视图。
- (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;
}
- (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;
}
}
}
#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 及相关代码。