前言
我們平時登入不同的平臺,總會有使用到Token的場景,比如用github賬戶登入掘金,這個時候我們肯定不會把自己的github賬號密碼給掘金, OAuth 就是這樣一套機制,用於各種免密授權登入場景,在便利的同時保證安全性,其實就是向平臺申請token授權。筆者最近在用spotify API二次開發自己的應用,所以也碰到使用者鑑權登入的場景,踩坑蠻多,也學到很多,所以整理下來,希望能幫助到大家?。
簡要的授權流程
- 使用者開啟客戶端以後,客戶端要求使用者給予授權(掘金登入選項:github登入)。
- 使用者同意給予客戶端授權(我點選github登入,登入通過)。
- 客戶端使用上一步獲得的授權,向認證伺服器申請令牌(掘金申請 access_token)。
- 認證伺服器對客戶端進行認證以後,確認無誤,同意發放令牌(github給他 access_token)。
- 客戶端使用令牌,向資源伺服器申請獲取資源(掘金拿著access_token 去換資源)。
- 資源伺服器確認令牌無誤,同意向客戶端開放資源(github校驗通行)。
掘金和github簡要舉例,幫助大家理解,接下來我用Spotify的鑑權詳細舉例吧
授權碼模式(authorization code)
你的網站需要獲得伺服器的授權,要現在對應的開發者申請賬號、祕鑰、重定向地址
- client_id:我們去spotify那裡註冊的
- client Secret: soptify給我們的祕鑰
- redirect_uri:認證伺服器把客戶端重定向去的一個 url(本地開發一般是loacalhost)
- response_type:需要你給我一個 code
- state:任意值,規範規請求和返回時都是一樣的值
首先示範一下如何在spotify develope申請開發者身份
這裡針對申請spotify開發者身份的流程介紹,圖文較多,不敢興趣的可以劃過,下面有我對OAth鑑權的流程介紹。
- 在這裡建立你的應用例項建立應用
- 登入後點選
create a client id
,生成一個專用的client_id
和client_secret
。
- 同時設定
Redirect URIs
,這是通過鑑權後重定向的地址,埠上執行你的應用,一定要填寫準確。
-
Scopes 許可權選擇,許可權選擇在進行授權申請之前,要先確定這個應用需要哪些許可權,確定好了再到授權過程中通過後端引數進行宣告。
Spotify對許可權進行了詳細的分類,全部的許可權如下:
Authentication 授權
授權的最終目的是獲取一個名為access_token
的值,然後用這個access_token
去獲取各種個樣的API資訊。
Spotify為了嚴格區分不同的用途和許可權,把這個access_token
的獲取方法分為了三種流程,各自的許可權、存活期都不同。
三種流程特點如下:
Authorization Code Flow
: 標準方法,可重新整理tokenClient Credentials Flow
: app級token,不可獲取使用者行為。Implicit Grant Flow
: 臨時授權。可獲取使用者行為,不可重新整理。存活期短。
如果沒有使用者的點選授權,那麼只能使用後兩者的授權模式。在使用者點選授權後,才能拿到使用者的行為種子做個性化開發,這也是推薦的開發者授權模式,接下來就是Authorization Code
授權流程:
- 向
/authorize
傳送GET請求,請求頭包括client_id
和redirect_uri
等 - 經
/authorize,accounts
判斷是有效client_id
,redirect_uri
,client_secret
(這裡注意必須和你申請應用填的完全一致)後,Spotify彈出頁面,使用者手動登入並點選允許授權 - Spotify把頁面跳轉至自己設定的callback網址,並明文傳輸一個
Code
碼 - 用
Code
碼向/token
傳送POST請求,並在header中包括一個動態生成並base64編碼的Authorization
字串,格式為Authorization: Basic *<base64 encoded client_id:client_secret>*
- 從Spotify獲得訪問令牌
access_token
和更新令牌refresh_token
(雙令牌) - 拿到授權碼acess_token後,每次向瀏覽器請求資源,請求頭都會加上
Authorization
欄位 - 在
access_token
過期後就不能再用了,這時請求伺服器會返回401狀態碼,這個時候就要用refresh token來更新token:用refresh_token
向/token
傳送POST請求,獲得新的access_token
。
聽起來有些彎彎繞繞?,其實本質上就是根據OAuth 2.0 標準協議,把服務商的資源,來授權給第三方應用訪問,常見於單點登入場景(SSO)
理論差不多是上面這些,還有看不懂的小夥伴可以搜有一下OAuth2.0協議,接下來我們開始寫程式碼了~?
編碼
首先我們的應用程式要獲得client_id
和client_secret
,肯定不能明文寫在程式碼裡,我們可以在本地終端寫入臨時的環境變數,通過node程式模組process
讀取到id和祕鑰。
process
物件是一個global
(全域性變數),提供有關資訊,控制當前Node.js
程式。作為一個物件,它對於Node.js
應用程式始終是可用的,故無需使用require()
。
Node程式怎麼寫入環境變數呢?這裡介紹兩種臨時環境變數的配置方法
配置Node環境變數
- Windows配置 臨時cmd 檢視環境變數,新增環境變數,刪除環境變數
#檢視環境變數
set SPOTIFY_CLIENT_ID
#如果不存在則新增環境變數
set SPOTIFY_CLIENT_ID=XXXX
set SPOTIFY_CLIENT_SECRET=YYYY
#環境變數追加值 set 變數名=%變數名%;變數內容 (你的應用路徑名)
set path=%path%;C:\web;C:\Tools
#需要刪除環境變數,直接=後面不寫入值
set SPOTIFY_CLIENT_ID=
set SPOTIFY_CLIENT_SECRET=
複製程式碼
Window cmd環境圖在這
臨時 (powershell) 檢視環境變數,新增環境變數,刪除環境變數#檢視是否存在
$env:SPOTIFY_CLIENT_ID
#如果不存在則新增環境變數
$env:SPOTIFY_CLIENT_ID="XXX"
$env:SPOTIFY_CLIENT_SECRET="YYY"
#環境變數追加值
$env:path=$env:path + ";C:\web;C:\Tools"
#刪除環境變數 del env:SPOTIFY_CLIENT_ID
#顯示所有的環境變數 ls env:
複製程式碼
powershell圖在這
這裡需要特別注意的是雖然通過 CMD 和 Powershell 都能修改環境變數,在不同終端的環境變數是不能共享的,(筆者在這裡踩了很久坑TT)即你在 CMD 可以設定SPOTIFY_CLIENT_ID="XXX"
,同時也可以在 Powershell 中設定 SPOTIFY_CLIENT_ID="YYY"
如果你只在cmd裡設定,Node環境裡是讀不到的!? 。並且,上面的環境設定只是臨時的,只針對當前執行視窗的環境有效。當終端執行視窗關閉以後,相關設定都會丟失。
設定好後就可以在node中通過process.env.REDIRECT_URI
讀取到賬戶、祕鑰資訊,接下來就是在後端寫鑑權需要的各種介面了
先上一張官網的授權流程圖,感受一下授權流程?
登入
/login
登入
let redirect_uri =
process.env.REDIRECT_URI ||
'http://localhost:8888/callback'
app.get('/login', function(req, res) {
res.redirect('https://accounts.spotify.com/authorize?' +
querystring.stringify({
response_type: 'code',
client_id: process.env.SPOTIFY_CLIENT_ID,
scope: 'user-read-private user-read-email user-read-recently-played user-top-read user-follow-read user-follow-modify playlist-read-private playlist-read-collaborative playlist-modify-public',
expires_in: 3600,
redirect_uri
}))
})
複製程式碼
這裡的scope看著寫,需要什麼許可權就配置什麼,許可權越多,能訪問的使用者資源就越多,使用者可以點選授權登入後可以在官網連結刪除授權。
access_token
- callback
app.get('/callback', function(req, res) {
let code = req.query.code || null
let authOptions = {
url: 'https://accounts.spotify.com/api/token',
form: {
code: code,
redirect_uri,
grant_type: 'authorization_code',
expires_in: 3600
},
headers: {
'Authorization': 'Basic ' + (new Buffer(
process.env.SPOTIFY_CLIENT_ID + ':' + process.env.SPOTIFY_CLIENT_SECRET
).toString('base64'))
},
json: true
}
request.post(authOptions, function(error, response, body) {
if(!error && response.statusCode === 200){
var access_token = body.access_token
var expires_in = body.expires_in
let uri = process.env.FRONTEND_URI || 'http://localhost:3000'
res.redirect(uri + '?access_token=' + access_token +'?expires_in=' + expires_in)
} else {
res.redirect(`/#${querystring.stringify({ error: 'invalid_token' })}`);
}
})
})
複製程式碼
若引數無誤,響應資料包格式:
若引數無誤,伺服器將返回一段JSON文字,包含以下引數:
- access_token:要獲取的Access Token。
- expires_in:Access Token的有效期,以秒為單位。
- refresh_token:用於重新整理Access Token 的 Refresh Token。
- scope:Access Token最終的訪問範圍,即使用者實際授予的許可權列表(使用者在授權頁面時,有可能會取消掉某些請求的許可權)。
- cookies: 讓瀏覽器記住客戶端,包含session_id
這個時候就會自動跳轉到最開始設定好的redirect_url
,這個時候這個url上執行著我的React應用,有了鑑權之後就可以愉快的使用使用者資訊,訪問呼叫spotify的API啦~
超時重新整理 refresh_token
refresh_token
顧名思義,refresh_token
就是起到重新整理token的作用,避免使用者再次點選授權進行驗證,那麼refresh_token
是怎麼和access_token
聯合起來使用的呢?
如果我們遇到了access_token
過期了,那麼我們需要使用refresh_token
去獲取一個新的access_token
,聽起來很簡單,那如果refresh_token
也過期了呢?這時使用者需要重新登入嗎?官網給出的解決方案很簡單,使用**refresh_token
來擴充套件access_token
的有效性。就是協調雙令牌
正確的做法即是兩個令牌都有自己的過期時間,因為access_token
是需要使用refresh_token
重新整理獲取,所以refresh_token
設定的過期時間要比access_token
時間長,那麼如何避免refresh_token
過期呢?辦法就是我們使用refresh_token
重新整理了access_token
後,將POST請求傳送到Accounts服務/api/token
端點,grant_type
註明是 refresh_token
那麼Spotify將返回新的access_token
。也返回新的refresh_token
,這樣兩個令牌的時間又得到了延長。這就保證了使用者在一個規定時間段只要訪問了應用,就可以享受無感知的重新整理體驗。
app.get('/refresh_token', function(req, res) {
// requesting access token from refresh token
const refresh_token = req.query.refresh_token;
let authOptions = {
url: 'https://accounts.spotify.com/api/token',
headers: {
'Authorization': `Basic` + (new Buffer(
process.env.SPOTIFY_CLIENT_ID + ':' + process.env.SPOTIFY_CLIENT_SECRET
).toString('base64')),
},
form: {
grant_type: 'refresh_token',
refresh_token,
},
json: true,
};
request.post(authOptions, function(error, response, body) {
if (!error && response.statusCode === 200) {
var access_token = body.access_token;
res.send({ access_token });
}
});
});
複製程式碼
axios發具體請求
因為每一次向服務端發請求,請求頭都需要帶上authorization
所以在我們的前端應用通過
window.location.href.match(/access_token=([^&]*)/)
拿到token
window.location.href.match(/expires_in=([\w]*)/)[1]
拿到過期時間
... 剩下需要的引數自己匹配 然後axios配置
headers: {
'Authorization': `Bearer ${access_token}`,
}
複製程式碼
就可以發請求拿到資料啦~ ?
動圖演示具體流程?
最後
這就是我的一次OAuth2鑑權登入的完整記錄啦,(找了蠻多資料發現敘述完整流程的很少,踩了蠻多坑,所以自己動手寫了個完整的 ~)
於是乎我們就可以愉快的藉助spotify的資料二次開發啦╰(* °▽°* )╯,很感謝看到這裡的掘友,希望本文能讓你收穫些東西,本文以spotify的開放API舉例,不玩spotify也沒關係,相信大家也能從本文了解一下OAth2的鑑權流程,碼字不易,希望你喜歡?