在 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(一)