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授权登录,仅支持应用内跳转到网页登录