以前的開發模式是以MVC
為主,但是隨着互聯網行業快速的發展逐漸的演變成了前后端分離,若項目中需要做登錄的話,那么token
成為前后端唯一的一個憑證。
token
即標志、記號的意思,在IT領域也叫作令牌。在計算機身份認證中是令牌(臨時)的意思,在詞法分析中是標記的意思。一般作為邀請、登錄系統使用。
token
其實說的更通俗點可以叫暗號,在一些數據傳輸之前,要先進行暗號的核對,不同的暗號被授權不同的數據操作。例如在USB1.1
協議中定義了4類數據包:token
包、data
包、handshake
包和special
包。主機和USB
設備之間連續數據的交換可以分為三個階段,第一個階段由主機發送token
包,不同的token
包內容不一樣(暗號不一樣)可以告訴設備做不同的工作,第二個階段發送data
包,第三個階段由設備返回一個handshake
包。
在HTTP請求中使用承載令牌來訪問OAuth 2.0
受保護的資源。擁有承載令牌的任何一方(“承載方”)都可以使用它訪問相關資源(無需證明擁有加密密鑰)。為了防止誤用,需要防止在存儲和傳輸中泄露承載令牌。
OAuth
允許客戶端通過獲取訪問令牌,它在“OAuth 2.0授權
”中定義框架“[RFC6749]
作為”表示訪問的字符串而不是使用資源直接服務的憑證。
該令牌由服務端允許的情況下,由客戶端通過某種方式向服務端發出請求,由服務端向客戶端發出,客戶機使用訪問令牌訪問由資源服務器承載的受保護的資源。該規范描述了當OAuth訪問令牌是承載令牌時,如何發出受保護的資源請求。
客戶端只需要擁有token
可以以任何一種方法傳遞token
,客戶端需要知道參數加密的密鑰,只需要存儲token
即可。
OAuth
為客戶端提供了一種方法來代表資源所有者訪問受保護的資源。在一般情況下,客戶機在訪問受保護的資源之前,必須首先從資源所有者獲得授權,然后將授權交換為訪問令牌。訪問令牌表示授權授予授予的范圍、持續時間和其他屬性。客戶機通過向資源服務器顯示訪問令牌來訪問受保護的資源。在某些情況下,客戶端可以直接向服務端顯示的發送自己的憑證。
+--------+ +---------------+
| |--(A)- Authorization Request ->| Resource |
| | | Owner |
| |<-(B)-- Authorization Grant ---| |
| | +---------------+
| |
| | +---------------+
| |--(C)-- Authorization Grant -->| Authorization |
| Client | | Server |
| |<-(D)----- Access Token -------| |
| | +---------------+
| |
| | +---------------+
| |--(E)----- Access Token ------>| Resource |
| | | Server |
| |<-(F)--- Protected Resource ---| |
+--------+ +---------------+
此方案的Authorization
頭字段的語法遵循[RFC2617]第2節
中定義的基本方案的用法。注意,與Basic
一樣,它不符合[RFC2617]第1.2節
中定義的通用語法,但與正在為HTTP 1.1 [HTTP- auth]
開發的通用身份驗證框架兼容,盡管它沒有遵循其中列出的反映現有部署的首選實踐。承載憑證的語法如下
b64toke = 1*( ALPHA / DIGIT / "-" / "." / "_" / "~" / "+" / "/" ) *"="
credentials = "Bearer" 1*SP b64token
客戶端應該使用帶有承載HTTP
授權方案的Authorization
請求頭字段使用承載令牌發出經過身份驗證的請求。資源服務器必須支持此方法
。
在Internet Engineering Task Force (IETF)
的白皮書中介紹Bearer Token
的使用方法,那么我們平時使用token
的時候姿勢是否正確。應該如何正確使用token
呢?
import axios from "axios";
axios.interceptors.request.use(config => {
if (store.state.token) {
config.headers.authorization = `Basic ${store.state.token}`;
}
return config;
});
照白皮書所說這樣才是正確使用token
的姿勢。小伙伴們平時你們使用token
的時候是這樣的嗎?
那么除了前端有明確的使用規范,那么服務端又應該怎樣有效的做好后端數據防護?在白皮書中同樣也有提到過。
根據OAuth 2.0動態客戶端注冊協議
該規范定義了向授權服務器動態注冊OAuth 2.0
客戶端的機制。注冊請求向授權服務器發送一組所需的客戶端元數據值(token
)。結果的注冊響應返回要在授權服務器上使用的客戶機標識符和為客戶機注冊的客戶機元數據值。然后,客戶機可以使用此注冊信息使用OAuth 2.0協議
與授權服務器通信。該規范還定義了一組通用客戶端元數據字段和值,供客戶端在注冊期間使用。
為了讓OAuth 2.0 [RFC6749]
客戶機利用OAuth 2.0
授權服務器,客戶機需要與服務器交互的特定信息,包括在該服務器上使用的OAuth 2.0
客戶端標識符。該規范描述了如何通過授權服務器動態注冊OAuth 2.0
客戶端來獲取此信息。
抽象的動態客戶端注冊流程
+--------(A)- Initial Access Token (OPTIONAL)
|
| +----(B)- Software Statement (OPTIONAL)
| |
v v
+-----------+ +---------------+
| |--(C)- Client Registration Request -->| Client |
| Client or | | Registration |
| Developer |<-(D)- Client Information Response ---| Endpoint |
| | or Client Error Response +---------------+
+-----------+
圖中所示的抽象OAuth 2.0客戶機動態注冊流描述了客戶機或開發人員與此規范中定義的端點之間的交互。此圖沒有顯示錯誤條件。這個流程包括以下步驟
- 可選地,向客戶端或開發人員發出初始訪問令牌,允許訪問客戶端注冊端點。向客戶端或開發人員發出初始訪問令牌的方法超出了本規范的范圍。
- 客戶端或開發人員可以選擇發布一個軟件聲明,以便與客戶端注冊端點一起使用。向客戶端或開發人員發出軟件聲明的方法超出了本規范的范圍。
- 客戶端或開發人員使用客戶端所需的注冊元數據調用客戶端注冊端點,如果授權服務器需要初始訪問令牌,則可以選擇包含來自(A)的初始訪問令牌。
- 授權服務器注冊客戶機並返回客戶端注冊的元數據, 在服務器上唯一的客戶端標識符,以及一組客戶端憑據,如客戶端機密(如果適用於此客戶端)。
授權類型與響應類型之間的關系
描述的“grant類型”和“響應類型”值是部分正交的,因為它們引用傳遞到OAuth
協議中不同端點的參數。但是,它們是相關的,因為客戶機可用的grant類型
影響客戶機可以使用的響應類型
,反之亦然。例如,包含授權代碼
的授權類型
值意味着包含代碼
的響應類型
值,因為這兩個值都定義為OAuth 2.0
授權代碼授權的一部分。因此,支持這些字段的服務器應該采取步驟,以確保客戶機不能將自己注冊到不一致的狀態,例如,通過向不一致的注冊請求返回無效的客戶機元數據
錯誤響應。
下表列出了這兩個字段之間的相關性。
+-----------------------------------------------+-------------------+
| grant_types value includes: | response_types |
| | value includes: |
+-----------------------------------------------+-------------------+
| authorization_code | code |
| implicit | token |
| password | (none) |
| client_credentials | (none) |
| refresh_token | (none) |
| urn:ietf:params:oauth:grant-type:jwt-bearer | (none) |
| urn:ietf:params:oauth:grant-type:saml2-bearer | (none) |
+-----------------------------------------------+-------------------+
向授予類型
或響應類型
參數引入新值的此文檔的擴展和概要文件必須記錄這兩種參數類型之間的所有通信。
如果發送任何人類可讀的字段時沒有使用語言標記,那么使用該字段的各方不能對字符串值的語言、字符集或腳本做出任何假設,而且字符串值必須按照在用戶界面中顯示的位置使用。為了促進互操作性,建議客戶端和服務器除了使用任何特定於語言的字段外,還使用不使用任何語言標記的人可讀字段,並且建議發送的任何不使用語言標記的人可讀字段包含適合在各種系統上顯示的值。
例如,軟件聲明可以包含以下聲明:
{
"software_id": "4NRB1-0XZABZI9E6-5SM3R",
"client_name": "Example Statement-based Client",
"client_uri": "https://client.example.net/"
}
以下非標准示例JWT
包括這些聲明,並且使用RS256
(僅用於顯示目的)進行了非對稱簽名。並等到如下加密字符串。
eyJhbGciOiJSUzI1NiJ9.
eyJzb2Z0d2FyZV9pZCI6IjROUkIxLTBYWkFCWkk5RTYtNVNNM1IiLCJjbGll
bnRfbmFtZSI6IkV4YW1wbGUgU3RhdGVtZW50LWJhc2VkIENsaWVudCIsImNs
aWVudF91cmkiOiJodHRwczovL2NsaWVudC5leGFtcGxlLm5ldC8ifQ.
GHfL4QNIrQwL18BSRdE595T9jbzqa06R9BT8w409x9oIcKaZo_mt15riEXHa
zdISUvDIZhtiyNrSHQ8K4TvqWxH6uJgcmoodZdPwmWRIEYbQDLqPNxREtYn0
5X3AR7ia4FRjQ2ojZjk5fJqJdQ-JcfxyhK-P8BAWBd6I2LLA77IG32xtbhxY
fHX7VhuU5ProJO8uvu3Ayv4XRhLZJY4yKfmyjiiKiPNe-Ia4SMy_d_QSWxsk
U5XIQl5Sa2YRPMbDRXttm2TfnZM1xx70DoYi8g6czz-CPGRi4SW_S2RKHIJf
IjoI3zTJ0Y2oe0_EJAiXbL6OyF9S5tKxDXV8JIndSA
加密字符串由頭,載荷以及密鑰通過一系列的速算法生成,加密字符串與頭,載荷以及密鑰息息相關,一但加密字符串稍有改動,則無法解析正確解析無法通過驗證。
通過加密字符串向授權服務器注冊客戶端。授權服務器為該客戶端分配一個惟一的客戶端標識符,可選地分配一個客戶端機密,並將請求中提供的元數據與已發布的客戶端標識符關聯起來。該請求包括在注冊期間為客戶端指定的任何客戶端元數據參數。授權服務器可以為客戶端元數據中遺漏的任何項提供默認值。
注冊端點,內容類型為application/json
。該HTTP Entity Payload
是一個由JSON組成的JSON
文檔對象和所有請求的客戶端元數據值作為頂級成員那個JSON
對象。
示例:
const Koa = require("koa");
const Router = require("koa-router");
const jwt = require("jsonwebtoken");
const jwtAuth = require("koa-jwt");
const secret = "it's a secret"; // 密鑰
const app = new Koa();
const router = new Router();
router.get('/api/login',async (ctx) => {
const {username,passwd} = ctx.query;
if(username === "aaron" && passwd == "123456"){
const token = jwt.sign({
data:{name:"Aaron",userId:"1"}, // 用戶信息
exp:Math.floor(Date.now()/1000)+60*60 // 過期時間
},secret);
ctx.body = {code:200,token};
}
else{
ctx.status = 401;
ctx.body = {code:0,message: "用戶名密碼錯誤"};
}
});
router.get("/api/userinfo",jwtAuth({secret}),async (ctx) => { // jwtAuth受保護路由
ctx.body = {code:200,data:{name:"Aaron",age:18}}
});
app.use(router.routes());
app.listen(3000);
因為最后生成的token
是通過base64
加密的,有些內容是可以反解的,所以千萬不要在數據里面添加有關數據的敏感信息。注意注意。。。