隨著Web應(yīng)用的發(fā)展,為了保證API通信的安全性倔监,很多項(xiàng)目在進(jìn)行設(shè)計(jì)時(shí)會(huì)采用JSON Web Token
(JWT
)的解決方案直砂。
JWT
是一種開(kāi)放標(biāo)準(zhǔn)(RFC 7519),它定義了一種緊湊且自包含的方式浩习,用于在各方之間安全地傳輸信息作為JSON
對(duì)象静暂。這種信息可以被驗(yàn)證和信任,因?yàn)樗菙?shù)字簽名的谱秽。
那么JWT
中的Token
到底是什么洽蛀?接下來(lái),我們將以登錄功能為例進(jìn)行Token
的分析疟赊。
登錄流程
很多小伙伴對(duì)登錄的流程已經(jīng)很熟悉了郊供,我們來(lái)看一個(gè)最基本的后臺(tái)系統(tǒng)的登錄流程
流程圖很清楚了,接下來(lái)我們使用 V2
和 Koa
來(lái)實(shí)現(xiàn)一個(gè)登錄過(guò)程近哟,來(lái)看看Token
到底是什么
Vue2
+ Koa
實(shí)現(xiàn)登錄
前端代碼
1. 前端點(diǎn)擊事件
數(shù)據(jù)的校驗(yàn)就忽略掉驮审,感興趣的同學(xué)可自行書寫或者找我要源碼,直接看點(diǎn)擊事件
handleLogin() {
this.$refs.loginForm.validate((valid) => {
if (valid) {
this.loading = true;
// 這里使用了VueX
this.$store
.dispatch("user/login", this.loginForm)
.then(() => {
this.$router.push({ path: this.redirect || "/" });
this.loading = false;
})
.catch(() => {
this.loading = false;
});
} else {
return false;
}
});
}
2. Vuex中的action
校驗(yàn)通過(guò)后觸發(fā)VueX
中User
模塊的Login
方法:
async login(context, userInfo) {
const users = {
username: userInfo.mobile,
password: userInfo.password
}
const token = await login(users)
// 在這里大家可以對(duì)返回的數(shù)據(jù)進(jìn)行更詳細(xì)的邏輯處理
context.commit('SET_TOKEN', token)
setToken(token)
}
3. 封裝的接口
export function login(data) {
return request({
url: '/login',
method: 'post',
data
})
}
以上三步吉执,是我們從前端向后端發(fā)送了請(qǐng)求并攜帶著用戶名和密碼疯淫,接下來(lái),我們來(lái)看看Koa
中是如何處理前端的請(qǐng)求的
Koa
處理請(qǐng)求
首先介紹一下Koa
:
Koa
基于Node.js
平臺(tái)戳玫,由Express
幕后的原班人馬打造熙掺,是一款新的服務(wù)端 web 框架
Koa
的使用極其簡(jiǎn)單,感興趣的小伙伴可以參考官方文檔嘗試用一下
Koa
官網(wǎng):https://koa.bootcss.com/index.html#introduction
1. 技術(shù)說(shuō)明
在當(dāng)前案例的koa
中咕宿,使用到了jsonwebtoken
的依賴包幫助我們?nèi)ゼ用苌珊徒饷?code>Token
2. 接口處理
const { login } = require("../app/controller/user")
const jwt = require("jsonwebtoken")
const SECRET = 'test_';
router.post('/login', async (ctx, next) => {
const { username, password } = ctx.request.body
// 這里是調(diào)用Controller中的login方法來(lái)跟數(shù)據(jù)庫(kù)中的數(shù)據(jù)作對(duì)比适掰,可忽略
const userList = await login(username, password)
if (!userList) {
// 這里的errorModel是自己封裝的處理錯(cuò)誤的模塊
ctx.body = new errorModel('用戶名或密碼錯(cuò)誤', '1001')
return
}
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓ ※ 重點(diǎn)看這里 ※ ↓↓↓↓↓↓↓↓↓↓↓↓↓↓
const token = jwt.sign({ userList }, SECRET, { expiresIn: "1h" })
ctx.body = {
success: true,
state: 200,
message: 'login success',
data: token
};
return;
})
關(guān)于 JWT
上面的重點(diǎn)代碼大家看到了,接下來(lái)具體給大家解釋下JWT
Jwt
由三部分組成:header
荠列、payload
类浪、signature
export interface Jwt {
header: JwtHeader;
payload: JwtPayload | string;
signature: string;
}
header
頭部
里面的包含的內(nèi)容有很多,比如用于指定加密算法的alg
肌似、指定加密類型的typ
费就,全部參數(shù)如下所示:
export interface JwtHeader {
alg: string | Algorithm;
typ?: string | undefined;
cty?: string | undefined;
crit?: Array<string | Exclude<keyof JwtHeader, 'crit'>> | undefined;
kid?: string | undefined;
jku?: string | undefined;
x5u?: string | string[] | undefined;
'x5t#S256'?: string | undefined;
x5t?: string | undefined;
x5c?: string | string[] | undefined;
}
payload
負(fù)載
payload
使我們存放信息的地方,里面包含了簽發(fā)者
川队、過(guò)期時(shí)間
力细、簽發(fā)時(shí)間
等信息
export interface JwtPayload {
[key: string]: any;
iss?: string | undefined;
sub?: string | undefined;
aud?: string | string[] | undefined;
exp?: number | undefined;
nbf?: number | undefined;
iat?: number | undefined;
jti?: string | undefined;
}
signature
簽名
signature
需要使用編碼后的header
和payload
以及我們提供的一個(gè)密鑰(SECRET
),然后使用header
中指定的簽名算法進(jìn)行簽名
關(guān)于 jwt.sign()
jwt.sign()
方法固额,需要三個(gè)基本參數(shù)和一個(gè)可選參數(shù):payload
眠蚂、secretOrPrivateKey
、options
和一個(gè)callback
export function sign(
payload: string | Buffer | object,
secretOrPrivateKey: Secret,
options: SignOptions,
callback: SignCallback,
): void;
payload
是我們需要加密的一些信息斗躏,這個(gè)參數(shù)對(duì)應(yīng)上面koa
代碼中的{ userList }
逝慧,而userList
則是我從數(shù)據(jù)庫(kù)中查詢得到的數(shù)據(jù)結(jié)果
secretOrPrivateKey
則是我們自己定義的秘鑰,用來(lái)后續(xù)驗(yàn)證Token
時(shí)所用
options
選項(xiàng)中有很多內(nèi)容,例如加密算法algorithm
笛臣、有效期expiresIn
等等
export interface SignOptions {
/**
* Signature algorithm. Could be one of these values :
* - HS256: HMAC using SHA-256 hash algorithm (default)
* - HS384: HMAC using SHA-384 hash algorithm
* - HS512: HMAC using SHA-512 hash algorithm
* - RS256: RSASSA using SHA-256 hash algorithm
* - RS384: RSASSA using SHA-384 hash algorithm
* - RS512: RSASSA using SHA-512 hash algorithm
* - ES256: ECDSA using P-256 curve and SHA-256 hash algorithm
* - ES384: ECDSA using P-384 curve and SHA-384 hash algorithm
* - ES512: ECDSA using P-521 curve and SHA-512 hash algorithm
* - none: No digital signature or MAC value included
*/
algorithm?: Algorithm | undefined;
keyid?: string | undefined;
/** expressed in seconds or a string describing a time span [zeit/ms](https://github.com/zeit/ms.js). Eg: 60, "2 days", "10h", "7d" */
expiresIn?: string | number | undefined;
/** expressed in seconds or a string describing a time span [zeit/ms](https://github.com/zeit/ms.js). Eg: 60, "2 days", "10h", "7d" */
notBefore?: string | number | undefined;
audience?: string | string[] | undefined;
subject?: string | undefined;
issuer?: string | undefined;
jwtid?: string | undefined;
mutatePayload?: boolean | undefined;
noTimestamp?: boolean | undefined;
header?: JwtHeader | undefined;
encoding?: string | undefined;
allowInsecureKeySizes?: boolean | undefined;
allowInvalidAsymmetricKeyTypes?: boolean | undefined;
}
callback
則是一個(gè)回調(diào)函數(shù)云稚,有兩個(gè)參數(shù),默認(rèn)返回Token
export type SignCallback = (
error: Error | null,
encoded: string | undefined,
) => void;
通過(guò)以上方法加密之后的結(jié)果就是一個(gè)Token
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjU3ZmVmMTY0ZTU0YWY2NGZmYzUzZGJkNSIsInhzcmYiOiI0ZWE1YzUwOGE2NTY2ZTc2MjQwNTQzZjhmZWIwNmZkNDU3Nzc3YmUzOTU0OWM0MDE2NDM2YWZkYTY1ZDIzMzBlIiwiaWF0IjoxNDc2NDI3OTMzfQ.PA3QjeyZSUh7H0GfE0vJaKW4LjKJuC3dVLQiY4hii8s
總結(jié)
在整個(gè)的Koa
中沈堡,用到了jsonwebtoken
這個(gè)依賴包静陈,里面有sign()
方法
而我們前端所得到的數(shù)據(jù)則是通過(guò)sign()
所加密出來(lái)的包含自定義秘鑰的一份用戶信息而已
至于用戶信息中有什么內(nèi)容,可以隨便處理诞丽,比如用戶的ID鲸拥、用戶名、昵稱僧免、頭像等等
那么這個(gè)Token
后續(xù)有什么用呢崩泡?
后續(xù)我們可以在前端的攔截器中配置這個(gè)Token
,讓每一次的請(qǐng)求都攜帶這個(gè)Token
猬膨,因?yàn)?code>Koa后續(xù)需要對(duì)每一次請(qǐng)求進(jìn)行Token
的驗(yàn)證
比如登錄成功后請(qǐng)求用戶的信息,獲取動(dòng)態(tài)路由呛伴,再通過(guò)前端的router.addRoutes()
將動(dòng)態(tài)路由添加到路由對(duì)象中去即可