在 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與參數,以下圖,跳轉去連接。
請求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(一)