草庐IT

Apple ID 登录

爱勤海之旅 2023-11-12 原文

Apple登录可以看做第三方登录的一种,即使用Apple ID登录,前提是你已经有了苹果开发者账号,直接进入主题吧。

添加App IDs

填写Bundle ID

勾选“Sign In with Apple”

创建Key
苹果将使用公钥/私钥对作为OAuth客户端机密,其中客户端机密实际上是一个签名的JWT,下一步需要向Apple注册新的私钥。

创建完成后会生成一个Key ID以及Key文件。下载key文件,其实就是一个.p8文件,双击可打开这个文件里面有需要的key,这个文件很重要,而且只能下载一次,请妥善保存!!!

生成客户端密钥(Client Secret)

苹果要求您自己从私钥中导出客户端密钥,而不是静态客户端密钥。他们使用JWT标准,使用带有P-256曲线和SHA256散列的椭圆曲线算法。换句话说,他们使用ES256JWT算法。有些JWT库不支持椭圆曲线方法,所以在开始尝试之前,请确保您的库支持椭圆曲线。RubyJWT库支持这种算法,因此我们将使用它来生成秘密。

首先,确保已安装Ruby,然后从命令行运行以下命令来安装JWT gem:

gem install jwt

根据上面准备的 Bundle IDTeam IDKey IDAuthKey_xxx.p8就可以生成client_secret(客户端密钥),我们简单创建一个txt文件,命名为secret_gen,然后将后缀改为.rb,即secret_gen.rb。模版内容如下:

require 'jwt'

key_file = ''
team_id = ''
client_id = ''
key_id = ''

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

headers = {
  'kid' => key_id
}

claims = {
	'iss' => team_id,
	'iat' => Time.now.to_i,
	'exp' => Time.now.to_i + 86400*180,
	'aud' => 'https://appleid.apple.com',
	'sub' => client_id,
}

token = JWT.encode claims, ecdsa_key, 'ES256', headers

puts token

该代码使用ES256算法生成JWT,其中包含少量声明。JWT将在6个月内到期,这是苹果允许的最长使用期限。如果您在每次用户进行身份验证时都生成一个新的客户端密钥JWT,那么您应该使用更短的到期日期,但这允许我们生成一次密钥并在示例应用程序中轻松使用。

这里将用到我们已经准备好的四个数据,现在您可以从命令行运行它,它将输出一个JWT:

ruby xxx/xx/xx/secret_gen.rb


这个eyJra.....RuAQ就是我们需要的客户端密钥,接下来我们验证下这个密钥是否有效。

在项目中配置苹果登录

首先导入头文件#import <AuthenticationServices/AuthenticationServices.h>,然后调起苹果授权页面

    if (@available(iOS 13.0, *)) {

        ASAuthorizationAppleIDProvider *appleIdProvider = [[ASAuthorizationAppleIDProvider alloc] init];
        ASAuthorizationAppleIDRequest *authAppleIDRequest = [appleIdProvider createRequest];
        /*
         //慎用 ASAuthorizationPasswordRequest
         //当启用ASAuthorizationPasswordRequest且停止使用Apple ID(真机-设置-账户-密码与安全性-使用您Apple ID的App-App列表-停止使用 Apple ID, 如果KeyChain里面没有登录信息且重新使用苹果授权登录(Sign in with Apple)会报未知错误
         ASAuthorizationPasswordRequest* authPasswordRequest = [[[ASAuthorizationPasswordProvider alloc] init] createRequest];
         
         NSMutableArray <ASAuthorizationRequest*> * array = [NSMutableArray arrayWithCapacity:2];
         if(authAppleIDRequest) [array addObject:authAppleIDRequest];
         if(authPasswordRequest) [array addObject:authPasswordRequest];
         
         NSArray <ASAuthorizationRequest*> * requests = [array copy];
         ASAuthorizationController* authorizationController = [[ASAuthorizationController alloc] initWithAuthorizationRequests:requests];
         */
        authAppleIDRequest.requestedScopes = @[ASAuthorizationScopeFullName, ASAuthorizationScopeEmail];
        
        //由ASAuthorizationAppleIDProvider创建的授权请求 管理授权请求的控制器
        ASAuthorizationController* authorizationController = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[authAppleIDRequest]];
        //设置授权控制器通知授权请求的成功与失败的代理
        authorizationController.delegate = self;
        //设置提供 展示上下文的代理,在这个上下文中 系统可以展示授权界面给用户
        authorizationController.presentationContextProvider = self;
        //在控制器初始化期间启动授权流
        [authorizationController performRequests];
        
    } 

代理回调ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding

//授权成功
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization API_AVAILABLE(ios(13.0))
{
    NSString *userID = nil;
    NSString *userName = nil;
    NSString *userEmail = nil;
    
    EVAppleLoginCredentialModel *model = [[EVAppleLoginCredentialModel alloc] init];
    
    if ([authorization.credential isKindOfClass:[ASAuthorizationAppleIDCredential class]]) {
        
        ASAuthorizationAppleIDCredential *credential = authorization.credential;
        
        //苹果用户唯一标识符,该值在同一个开发者账号下的所有App下是一样的,开发者可以用该唯一标识符与自己后台系统的账号体系绑定起来。
        userID = credential.user;
    
        //苹果用户信息 如果授权过,无法再次获取该信息
        userEmail = credential.email;
        NSPersonNameComponents *fullName = credential.fullName;
        userName = [NSString stringWithFormat:@"%@%@", fullName.familyName, fullName.givenName];
        
        //用于判断当前登录的苹果账号是否是一个真实用户,取值有:unsupported、unknown、likelyReal
        ASUserDetectionStatus realUserStatus = credential.realUserStatus;

        //服务器验证需要使用的参数
        NSString *authorizationCode = [[NSString alloc] initWithData:credential.authorizationCode encoding:NSUTF8StringEncoding];
        NSString *identityToken = [[NSString alloc] initWithData:credential.identityToken encoding:NSUTF8StringEncoding];
        
        NSLog(@"authorization [ASAuthorizationAppleIDCredential] successfully");
        NSLog(@"authorization userID: %@", userID);
        NSLog(@"authorization userName: %@", userName);
        NSLog(@"authorization userEmail: %@", userEmail);
        NSLog(@"authorization realUserStatus: %@", @(realUserStatus));
        NSLog(@"authorization authorizationCode: %@", authorizationCode);
        NSLog(@"authorization identityToken: %@", identityToken);
        
    } else if ([authorization.credential isKindOfClass:[ASPasswordCredential class]]) {
        //用户登录使用现有的密码凭证
        ASPasswordCredential *passwordCredential = authorization.credential;

        //密码凭证对象的用户标识 用户的唯一标识和密码
        userID = passwordCredential.user;
        NSString *password = passwordCredential.password;
    
        NSLog(@"authorization [ASPasswordCredential] successfully");
        NSLog(@"authorization userID: %@", userID);
        NSLog(@"authorization password: %@", password);
        
        model.user = passwordCredential.user;
        
        userName = @"";
        userEmail = @"";
        
    } else {
        assert(0);
    }
    
    //数据校验通过后,将这些数据发送给服务器进行验证,等待服务器的回应
    //如果用户选择隐藏邮箱的,email获取不到,其他数据正常,服务端匹配标准userID
    if (![ZJUtils isEmpty:userID]) {

        //在授权成功的回调中拿到服务器所需要的参数传给后台
        //至此我们所需要做的已经完成了,看后台的验证就行了
        if (self.appleLoginBlock) {
            self.appleLoginBlock(0, @"苹果授权成功", model);
        }
        
    } else {
        //授权数据异常
        if (self.appleLoginBlock) {
            self.appleLoginBlock(-1, @"苹果授权失败", nil);
        }
    }
}

//授权失败
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)error API_AVAILABLE(ios(13.0))
{
    NSString *errorMsg = nil;
    UIWindow *window = [UIApplication sharedApplication].keyWindow;
    switch (error.code) {
        case ASAuthorizationErrorCanceled:
            errorMsg = @"用户取消了授权请求";
            break;
        case ASAuthorizationErrorFailed:
            errorMsg = @"授权请求失败";
            break;
        case ASAuthorizationErrorInvalidResponse:
            errorMsg = @"授权请求响应无效";
            break;
        case ASAuthorizationErrorNotHandled:
            errorMsg = @"未能处理授权请求";
            break;
        case ASAuthorizationErrorUnknown:
            errorMsg = @"授权请求失败未知原因";
            break;
    }
    NSLog(@"苹果授权状态:%@", errorMsg);
}

#pragma mark ASAuthorizationControllerPresentationContextProviding
- (ASPresentationAnchor) presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller API_AVAILABLE(ios(13.0))
{
    return self.view.window;
}

- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization 回调中我们拿到 authorizationCode 作为验证的参数。

打开postman我们测试下:

https://appleid.apple.com/auth/token?client_id=xxx&client_secret=xxx&code=xxx&grant_type=authorization_code&redirect_uri=https://appleid.apple.com

到这里就基本完成了,其实苹果登录大部分工作都是后台在做,包括client_secret生成等,我们客户端只需要在拿到苹果授权的数据给后端就可以了,后端去做校验。

参考:What the Heck is Sign In with Apple?

有关Apple ID 登录的更多相关文章

  1. ruby - 使用 Ruby 和 Mechanize 登录网站 - 2

    我需要从站点抓取数据,但它需要我先登录。我一直在使用hpricot成功地抓取其他网站,但我是使用mechanize的新手,我真的对如何使用它感到困惑。我看到这个例子经常被引用:require'rubygems'require'mechanize'a=Mechanize.newa.get('http://rubyforge.org/')do|page|#Clicktheloginlinklogin_page=a.click(page.link_with(:text=>/LogIn/))#Submittheloginformmy_page=login_page.form_with(:act

  2. ruby-on-rails - 使用用户或管理员模型和 Basecamp 样式子域设计登录 - 2

    我为Devise用户和管理员提供了不同的模型。我也在使用Basecamp风格的子域。除了我需要能够以用户或管理员身份进行身份验证的一些Controller和操作外,一切都运行良好。目前我有authenticate_user!在我的application_controller.rb中设置,对于那些只有管理员才能访问的Controller和操作,我使用skip_before_filter跳过它。不幸的是,我不能简单地指定每个Controller的身份验证要求,因为我仍然需要一些Controller和操作才能被用户或管理员访问。我尝试了一些方法都无济于事。看来,如果我移动authentica

  3. ruby - 如何使用 omniauth/oauth 对每秒登录数进行基准测试? ( ruby +rspec) - 2

    我想用一个(自己的)omniauth提供商来衡量每秒可以登录多少次。我需要了解此omniauth/oauth请求的性能如何,以及此身份验证是否具有可扩展性?到目前为止我得到了什么:defperformance_auth(user_count=10)bm=Benchmark.realtimedouser_count.timesdo|n|forkdoclick_on'Logout'omniauth_config_mock(:provider=>"foo",:uid=>n,:email=>"foo#{n}@example.net")visit"/account/auth/foo/"enden

  4. ruby - 如何使用 Ruby 和 eventmachine 登录? - 2

    我正在使用Ruby和Eventmachine库编写一个应用程序。我真的很喜欢非阻塞I/O和事件驱动系统的想法,我遇到的问题是日志记录。我正在使用Ruby的标准记录器库。并不是说日志记录需要永远,但它似乎不应该阻塞,但它确实阻塞了。是否有某个库将Ruby的标准记录器实现扩展为非阻塞的,或者我应该只调用EM::defer进行日志记录调用?有没有办法让eventmachine已经为我做这件事? 最佳答案 我最终将记录器包装在一个启动线程并具有FIFO队列的单例类中。日志记录会将日志信息转储到队列中,线程只是循环,从队列中拉出内容并使用真正

  5. ruby-on-rails - 尝试登录和使用 heroku 时无法识别 ruby​​.exe - 2

    当尝试创建一个heroku应用程序并通过git推送到它时,我收到以下错误:$herokucreate'"C:\ProgramFiles\ruby-1.9.2\bin\ruby.exe"isnotrecognizedasaninternalorexternalcommand,operableprogramorbatchfile.但是,$ruby-vruby1.9.3p125[i386-mingw32]我已经检查了PATH环境,它肯定包含“C:\ProgramFiles(x86)\ruby-1.9.2\bin”。同样有趣的是,当导航到该目录时,它实际上并不包含名为ruby​​.exe的文件

  6. ruby-on-rails - 使用 Ruby on Rails 设计 - 强制用户在首次登录时更改密码 - 2

    我有一个运行Devise的RoR应用程序(Rails4.2、Ruby2.2.0)。我已对其进行设置,以便管理员用户(标识我添加到用户模型的“is_admin”bool值)能够创建新的用户帐户,为他们提供生成的密码和确认电子邮件。这一切都正常工作。我还添加了datetime列pass_changed,当用户更改密码时应该更新它,然后检查created_at以确保自帐户创建以来密码已更改。如果两个日期相同,则用户将被重定向到密码更改表单。我编写了一个程序来检查用户是否更改了密码并将其放在我的application_controller.rb中:defcheck_changed_pass@u

  7. ruby-on-rails - 如何使 ActiveRecord::Schema.define 不登录到标准输出? - 2

    我在文档中看不到这个,但我认为这是一个已解决的问题。我在Rails之外使用ActiveRecord,我的脚本加载了从另一个应用程序转储的schema.rb。我想加载此模式而不将迁移输出转储到标准输出,但替换ActiveRecord::Base.logger不会关闭它。我应该覆盖什么来阻止噪音? 最佳答案 技巧显然在ActiveRecord::Migration中:ActiveRecord::Migration.verbose=false这使得迁移不会将信息输出到$stdout。有一个名为.suppress_messages的便捷包装

  8. Win10 / 11新电脑最简单跳过联网激活和使用本地账户登录方法 - 2

    跳过联网激活:OOBE界面直接按Ctrl+Shift+F3进入审核模式。这样就可以直接进入系统进行一些硬件测试等,而不用联网激活导致新机无法退货。需要注意的是,在审核模式下进行的一些操作都会保留,并不会在退出后自动还原!安装的软件在正常开机进系统后还会看见!如果电脑确实没连互联网又不想强行跳过OOBE(网上很多教程会叫你直接结束OOBE进程,但这是不推荐的,因为一些厂商自带优化程序和系统初始化设置在后面都会应用,对于笔记本跳过的话你会发现驱动和内置应用都没有装上。其实这部分脚本就在系统盘的Recovery隐藏文件夹下),可以参考以下方式:https://www.landiannews.com/

  9. Centos7-yum安装mysql-修改密码-无密码登录-安全配置 - 2

    目录1、yum安装mysql修改密码(1)在mysql里面修改(2)第二种方式,利用mysqladmin修改密码2、没有密码,登录mysql修改密码3、mysql的安全设置1、yum安装mysql在CentOS中默认安装有MariaDB(MySQL的一个分支),安装完成之后可以直接覆盖MariaDB。rpm-qa|grepmariadb查询是否安装了mariadbrpm-e--nodepsmariadb-libs-5.5.60-1.el7_5.x86_64卸载mariadwgethttp://dev.mysql.com/get/mysql57-community-release-el7-11.

  10. ruby-on-rails - "Given that I' m登录的 cucumber 步骤定义" - 2

    我有一个cucumber步骤:鉴于我已登录我不明白我应该如何将它作为一个步骤定义来实现。谁能指出我正确的方向、教程、博客等。 最佳答案 这是我的做法。Given/^Ihaveone\s+user"([^\"]*)"withemail"([^\"]*)"andpassword"([^\"]*)"$/do|username,email,password|@user=User.new(:email=>email,:username=>username,:password=>password,:password_confirmation=>

随机推荐