在 WWDC19 大会上,苹果公司推出了一项有意思的内容,即 “Sign In with Apple”。这项由苹果提供的认证服务,可让开发者容许用户使用 Apple Id 来登陆他们的应用程序,Sign In with Apple使用OAuth登陆受权标准。 javascript
本文将介绍使用苹果登陆的整个流程,并演示如何用Node.js在Web端接入苹果三方登陆。html
Sign in with Apple使用双重验证,简单说就是当你首次使用Apple登陆一个设备时,在输入Apple id和密码以后,还须要在其余已登陆的Apple设备上确认受权,并输入已登陆设备上提供的验证码进行验证。前端
有了双重认证,只能经过您信任的设备(如 iPhone、iPad、Apple Watch 或 Mac)才能访问您的账户。首次登陆一台新设备时,您须要提供两种信息:您的密码和自动显示在您的受信任设备上的六位验证码。输入验证码后,您即确认您信任这台新设备。例如,若是您有一台 iPhone 而且要在新购买的 Mac 上首次登陆您的账户,您将收到提示信息,要求您输入密码和自动显示在您 iPhone 上的验证码。因为只输入密码再也不可以访问您的账户,所以双重认证显著加强了 Apple ID 以及全部经过 Apple 储存的我的信息的安全性。java
登陆后,系统将不会再次要求您在这台设备上输入验证码,除非您彻底退出登陆账户、抹掉设备数据或出于安全缘由而须要更改密码。当您在 Web 上登陆时,能够选择信任您的浏览器,这样当您下次从这台电脑登陆时,系统就不会要求您输入验证码。node
](https://www.jianshu.com/p/f10...ios
当配置结束后咱们将得到咱们所需的两个文件、三个ID、和一个URL链接,以下(演示用,非正确)git
redirectURI = 'https://abc.baidu.com/appleAuth' // 本身设置的重定向域名,可添加多个 webClientId = 'com.baidu.abc.signInWithApple'; // 设置的client_id,通常是域名的反写 teamId = 'JI87S9KI7D'; // 10个字符的team_id keyId = 'KOI98S78J6'; // 获取的10个字符的密钥标识符
JWT
,做为请求Token时的参数之一apple-developer-domain-association.txt
文本放在项目代码中,做为帐号配置过程当中验证用,保证浏览器url输入https://abc.baidu.com/.well-known/apple-developer-domain-association.txt
时,能外网访问到此文本中的内容,完成后点击苹果开发者帐号配置过程当中的验证按钮(具体操做参考上面推荐的配置文章),经过后可进行正常开发调试。验证经过后可删除此文件。正式开发前咱们能够先了解下OAuth 2.0的标准,OAuth是一个关于受权的开放网络标准,apple登陆正是使用了此标准,若是你了解此标准的受权流程,在下面的开发中会以为很熟悉,OAuth流程大概以下:github
- 用户访问客户端,后者将前者导向认证服务器。
- 用户选择是否给予客户端受权。
- 假设用户给予受权,认证服务器将用户导向客户端事先指定的"重定向URI"(redirection URI),同时附上一个受权码。
- 客户端收到受权码,附上早先的"重定向URI",向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见
- 认证服务器核对了受权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)。
更多关于OAuth的知识可点击查阅此篇文章。web
苹果开发者文档提供了两篇在Web端接入苹果登陆相关的文档 ,以下,一篇是前端开发文档Sign in with Apple JS ,一篇是服务端开发文档Sign in with Apple REST API ,可点击连接查阅详细内容。算法
前端
`<script type="text/javascript" src="https://appleid.cdn-apple.com...;></script>
`
mate
标签的content
属性中写入相关配置帐号<html> <head> <meta name="appleid-signin-client-id" content="com.baidu.abc.signInWithApple"> <meta name="appleid-signin-scope" content="[SCOPES]"> <meta name="appleid-signin-redirect-uri" content="https://abc.baidu.com/appleAuth"> <meta name="appleid-signin-state" content="[STATE]"> </head> <body> <div id="appleid-signin" data-color="black" data-border="true" data-type="sign in"></div> <script type="text/javascript" src="https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js"></script> </body> </html>
AppleID.auth.init
方法,将配置信息以对象的形式传进去,自动跳转到受权页<html> <head> </head> <body> <script type="text/javascript" src="https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js"></script> <div id="appleid-signin" data-color="black" data-border="true" data-type="sign in"></div> <script type="text/javascript"> AppleID.auth.init({ clientId : '[CLIENT_ID]', scope : '[SCOPES]', redirectURI: '[REDIRECT_URI]', state : '[STATE]' }); </script> </body> </html>
官方文档对参数的定义如上图跳转去链接
这里面只有client_id
,redirect_uri
,是必须的,其余若是不设会自动设置默认值。
你可使用官方提供的按钮,固然也能够不用,当你点击登陆按钮后会实际会跳转到一下地址,你能够选择直接手动拼接跳转受权页地址。 https://appleid.apple.com/auth/authorize?client_id=[CLIENT_ID]&redirect_uri=[REDIRECT_URI]&response_type=[RESPONSE_TYPE]&scope=[SCOPES]&response_mode=[RESPONSE_MODE]&state=[STATE]
若是手动拼接的话 response_type
应设为 code
, response_mode
应设为form_post
,
当用户给予受权后,apple服务器将发起一个POST请求至当时设置的redirectURI
,同时附上一个受权码code
,id_token
可用于刷新token,这里的id_token
字段只有经过验证后才会有,首次请求并无这个字段,首次验证经过后再次登陆可直接经过解析这个id_token
来得到用户惟一标识,这里首次登陆,咱们将只有code
和state
,以下图
下图是官方文档对请求参数的解释跳转去链接,只有用户取消受权时才会返回惟一一个错误码user_cancelled_authorize
*值得注意的是当用户首次登陆时,apple将返回给咱们user
字段(如上图),里面有用户名和邮箱(或匿名邮箱),咱们应该将用户信息保存在服务端,与最终获取的用户惟一标识相对应。
在首次登陆事后咱们将永远没法再次获取用户信息,只有用户手动取消appleId在该程序上的登陆,并等待一段时间再次登陆时才会从新发送用户信息,因此当咱们首次请求时应及时把用户信息保存下来,以下图,跳转去连接
接下来咱们须要经过上步获取的受权码去获取身份令牌,这须要咱们在服务端去发起一个请求,请求url与参数,以下图,跳转去连接。
请求url为POST https://appleid.apple.com/auth/token
获取令牌咱们须要传如下几个参数
grant_type
:'authorization_code'为获取令牌client_id
:client_idredirect_uri
:redirect_uricode
:上一步获取到的受权码,codeclient_secret
:一个生成的JWT,若是不了解可自行查阅有关JWT的知识刷新令牌咱们须要传如下参数
grant_type
:'refresh_token'为刷新令牌client_id
:client_idclient_secret
:client_secret,refresh_token
:上一步获取到的id_token在此过程当中,最重要的就是client_secret
参数,为生成JWT,官网文档对JWT生成的相关条件以下图,可跳转去链接
在Node
代码中咱们使用 Node 的jsonwebtoken
库去生成jwt,代码以下。
规定生成的JWT最长期限为6个月,你能够手动生成 JWT ,用在项目里,但必须在将要过时前更新它,咱们把生成 JWT 的代码写在程序里,每次都从新生成一个JWT。
// 生成JWT const jwt = require('jsonwebtoken'); const fs = require('fs'); const path = require('path'); // apple开发者帐号配置下载的AuthKey_XHGXCP8B9S.p8文件 const PRIVATEKEY = fs.readFileSync(path.join(__dirname, './AuthKey_XH******9S.txt'), {encoding: 'utf-8'}); const TEARM_ID = 'K5******G8'; const CLIENT_ID = 'com.baidu.abc.signInWithApple'; const KEY_ID = 'XH******9S'; async getClientSecret() { const headers = { alg: 'ES256', kid: KEY_ID }; const timeNow = Math.floor(Date.now() / 1000); const claims = { iss: TEARM_ID, aud: 'https://appleid.apple.com', sub: CLIENT_ID, iat: timeNow, exp: timeNow + 15777000 }; const token = jwt.sign(claims, PRIVATEKEY, { algorithm: 'ES256', header: headers // expiresIn: '24h' }); return token; }
接下来咱们须要在服务端写一个api接口去接收apple发起的post请求,拿到请求参数后在服务端发起/auth/token
请求去请求access token,代码以下(thinkjs
编写)
const axios = require('axios'); const qs = require('qs'); const Base = require('./base.js'); export default class extends think.Controller { // appleAuth接口 async appleAuthAction() { const body = this.post(); // 获取token,刷新传grant_type:refresh_token与refresh_token const params = { grant_type: 'authorization_code', // refresh_token authorization_code code: body.code, redirect_uri: [REDIRECT_URI], client_id: [CLIENT_ID], client_secret: this.getClientSecret() // refresh_token:body.id_token }; const token = await this.authToken(params); // verifyIdToken为解密获取的id_token信息 const jwtClaims = await this.verifyIdToken(token.data.id_token, [CLIENT_ID]); this.success({ data: token.data, verifyData: jwtClaims }); } // 发起请求 async authToken(params) { return axios.request({ method: 'POST', url: 'https://appleid.apple.com/auth/token', data: qs.stringify(params), headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }); } };
请求成功后将返回 token ,以下图
<!---->
其中咱们用到的verifyIdToken
方法就是对该id_token
解密,首先咱们须要经过apple提供GET https://appleid.apple.com/auth/keys
接口获取公钥,跳转去连接
而后咱们用jwt.verify
经过公钥解密id_token
,代码以下
const NodeRSA = require('node-rsa'); // 获取公钥 async getApplePublicKey() { let res = await axios.request({ method: "GET", url: "https://appleid.apple.com/auth/keys", }) let key = res.data.keys[0] const pubKey = new NodeRSA(); pubKey.importKey({ n: Buffer.from(key.n, 'base64'), e: Buffer.from(key.e, 'base64') }, 'components-public'); return pubKey.exportKey(['public']); }; // 经过公钥和RS256算法解密id_token async verifyIdToken(id_token, client_id) { const applePublicKey = await this.getApplePublicKey(); const jwtClaims = jwt.verify(idToken, applePublicKey, { algorithms: 'RS256' }); return jwtClaims; };
解密后获得的verify.sub
就是用户apple帐号登陆在该程序中的惟一标识,咱们能够把它存到程序的数据库中与用户信息作映射,用于标识用户身份。
终于咱们完成了整个 apple 第三方登陆流程,获得了咱们须要的用户惟一标识与用户信息,更加完善了咱们项目的登陆模块。
文中 demo 演示的具体代码已经上传到 Github 中,可直接下载运行体验,但未上传全部帐号相关信息,你须要有一个 apple 开发者帐号哦!https://github.com/wwenj/Sign-in-with-Apple-for-node
可在咱们项目上体验apple登陆哦,声享
127.0.0.1
的,咱们开发过程当中能够经过配置本地 host ,将域ip指向本地。What the Heck is Sign In with Apple
Sgin in with Apple NODE
Sign in with Apple JS
Sign in with Apple REST API
Sign In With Apple(一)