在 WWDC19 大会上,苹果公司推出了一项有意思的内容,即 “Sign In with Apple”。这项由苹果提供的认证服务,可让开发者容许用户使用 Apple Id 来登陆他们的应用程序,Sign In with Apple使用OAuth登陆受权标准。javascript
本文将介绍使用苹果登陆的整个流程,并演示如何用NODE
在Web端接入苹果三方登陆。html
有了双重认证,只能经过您信任的设备(如 iPhone、iPad、Apple Watch 或 Mac)才能访问您的账户。首次登陆一台新设备时,您须要提供两种信息:您的密码和自动显示在您的受信任设备上的六位验证码。输入验证码后,您即确认您信任这台新设备。例如,若是您有一台 iPhone 而且要在新购买的 Mac 上首次登陆您的账户,您将收到提示信息,要求您输入密码和自动显示在您 iPhone 上的验证码。前端
因为只输入密码再也不可以访问您的账户,所以双重认证显著加强了 Apple ID 以及全部经过 Apple 储存的我的信息的安全性。java
登陆后,系统将不会再次要求您在这台设备上输入验证码,除非您彻底退出登陆账户、抹掉设备数据或出于安全缘由而须要更改密码。当您在 Web 上登陆时,能够选择信任您的浏览器,这样当您下次从这台电脑登陆时,系统就不会要求您输入验证码。node
当咱们拥有一个苹果开发者帐号后,须要进行相关配置来得到咱们在web端接入apple登陆时,所须要的一些id和文件,并作一些相关验证,此过程很是繁琐,此篇文章对配置流程有很详细的讲解,能够点击查阅What the Heck is Sign In with Apple?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个字符的密钥标识符
复制代码
一个以.p8结尾的文本文件,里面是生成的密钥,用做生成JWT
,做为请求Token时的参数之一github
另外一个apple-developer-domain-association.txt
文本放在项目代码中,做为帐号配置过程当中验证用,保证浏览器url输入https://abc.baidu.com/.well-known/apple-developer-domain-association.txt
时,能外网访问到此文本中的内容,完成后点击苹果开发者帐号配置过程当中的验证按钮(具体操做参考上面推荐的配置文章),经过后可进行正常开发调试。验证经过后可删除此文件。 web
正式开发前咱们能够先了解下OAuth 2.0的标准,OAuth是一个关于受权的开放网络标准,apple登陆正是使用了此标准,若是你了解此标准的受权流程,在下面的开发中会以为很熟悉,OAuth流程大概以下:算法
- 用户访问客户端,后者将前者导向认证服务器。
- 用户选择是否给予客户端受权。
- 假设用户给予受权,认证服务器将用户导向客户端事先指定的"重定向URI"(redirection URI),同时附上一个受权码。
- 客户端收到受权码,附上早先的"重定向URI",向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见
- 认证服务器核对了受权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)。
更多关于OAuth的知识可点击查阅此篇文章。
苹果开发者文档提供了两篇在Web端接入苹果登陆相关的文档 ,以下,一篇是前端开发文档Sign in with Apple JS ,一篇是服务端开发文档Sign in with Apple REST API ,可点击连接查阅详细内容。
前端
<script type="text/javascript" src="https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js"></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与参数,以下图,跳转去连接。
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
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 开发者帐号哦!github.com/wwenj/Sign-…
可在咱们项目上体验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(一)