隨著Web應(yīng)用的發(fā)展,為了保證API通信的安全性,很多項目在進(jìn)行設(shè)計時會采用JSON Web Token(JWT)的解決方案。
JWT是一種開放標(biāo)準(zhǔn)(RFC 7519),它定義了一種緊湊且自包含的方式,用于在各方之間安全地傳輸信息作為JSON對象。這種信息可以被驗證和信任,因為它是數(shù)字簽名的。
那么JWT中的Token到底是什么?接下來,我們將以登錄功能為例進(jìn)行Token的分析。
登錄流程
很多小伙伴對登錄的流程已經(jīng)很熟悉了,我們來看一個最基本的后臺系統(tǒng)的登錄流程

流程圖很清楚了,接下來我們使用 V2 和 Koa 來實現(xiàn)一個登錄過程,來看看Token到底是什么
Vue2 + Koa 實現(xiàn)登錄
前端代碼
1. 前端點擊事件
數(shù)據(jù)的校驗就忽略掉,感興趣的同學(xué)可自行書寫或者找我要源碼,直接看點擊事件
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
校驗通過后觸發(fā)VueX中User模塊的Login方法:
async login(context, userInfo) {
const users = {
username: userInfo.mobile,
password: userInfo.password
}
const token = await login(users)
// 在這里大家可以對返回的數(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ā)送了請求并攜帶著用戶名和密碼,接下來,我們來看看Koa中是如何處理前端的請求的
Koa 處理請求
首先介紹一下Koa:
Koa基于Node.js平臺,由Express幕后的原班人馬打造,是一款新的服務(wù)端 web 框架
Koa的使用極其簡單,感興趣的小伙伴可以參考官方文檔嘗試用一下
Koa官網(wǎng):https://koa.bootcss.com/index.html#introduction
1. 技術(shù)說明
在當(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方法來跟數(shù)據(jù)庫中的數(shù)據(jù)作對比,可忽略
const userList = await login(username, password)
if (!userList) {
// 這里的errorModel是自己封裝的處理錯誤的模塊
ctx.body = new errorModel('用戶名或密碼錯誤', '1001')
return
}
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓ ※ 重點看這里 ※ ↓↓↓↓↓↓↓↓↓↓↓↓↓↓
const token = jwt.sign({ userList }, SECRET, { expiresIn: "1h" })
ctx.body = {
success: true,
state: 200,
message: 'login success',
data: token
};
return;
})
關(guān)于 JWT
上面的重點代碼大家看到了,接下來具體給大家解釋下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ā)者、過期時間、簽發(fā)時間等信息
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以及我們提供的一個密鑰(SECRET),然后使用header 中指定的簽名算法進(jìn)行簽名
關(guān)于 jwt.sign()
jwt.sign()方法,需要三個基本參數(shù)和一個可選參數(shù):payload、secretOrPrivateKey、options和一個callback
export function sign(
payload: string | Buffer | object,
secretOrPrivateKey: Secret,
options: SignOptions,
callback: SignCallback,
): void;
payload是我們需要加密的一些信息,這個參數(shù)對應(yīng)上面koa代碼中的{ userList },而userList則是我從數(shù)據(jù)庫中查詢得到的數(shù)據(jù)結(jié)果
secretOrPrivateKey則是我們自己定義的秘鑰,用來后續(xù)驗證Token時所用
options選項中有很多內(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則是一個回調(diào)函數(shù),有兩個參數(shù),默認(rèn)返回Token
export type SignCallback = (
error: Error | null,
encoded: string | undefined,
) => void;
通過以上方法加密之后的結(jié)果就是一個Token
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjU3ZmVmMTY0ZTU0YWY2NGZmYzUzZGJkNSIsInhzcmYiOiI0ZWE1YzUwOGE2NTY2ZTc2MjQwNTQzZjhmZWIwNmZkNDU3Nzc3YmUzOTU0OWM0MDE2NDM2YWZkYTY1ZDIzMzBlIiwiaWF0IjoxNDc2NDI3OTMzfQ.PA3QjeyZSUh7H0GfE0vJaKW4LjKJuC3dVLQiY4hii8s
總結(jié)
在整個的Koa中,用到了jsonwebtoken這個依賴包,里面有sign()方法
而我們前端所得到的數(shù)據(jù)則是通過sign()所加密出來的包含自定義秘鑰的一份用戶信息而已
至于用戶信息中有什么內(nèi)容,可以隨便處理,比如用戶的ID、用戶名、昵稱、頭像等等
那么這個Token后續(xù)有什么用呢?
后續(xù)我們可以在前端的攔截器中配置這個Token,讓每一次的請求都攜帶這個Token,因為Koa后續(xù)需要對每一次請求進(jìn)行Token的驗證
比如登錄成功后請求用戶的信息,獲取動態(tài)路由,再通過前端的router.addRoutes()將動態(tài)路由添加到路由對象中去即可