koa-jwt源码解析

介绍

Koa middleware for validating JSON Web Tokens.

意为Koa 中间件用于验证 JSON Web Tokens.。主要使用了jsonwebtoken这个库编码和验证。该库主要验证jwt来保证后续中间件执行的安全性。

demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var Koa = require("koa");
var jwt = require("koa-jwt");

var app = new Koa();

// 验证token是否有效,过滤'/public'开头的的url
app.use(jwt({ secret: "shared-secret" }).unless({ path: [/^\/public/] }));

// 不被保护的中间件
app.use(function (ctx, next) {
if (ctx.url.match(/^\/public/)) {
ctx.body = "unprotected\n";
} else {
return next();
}
});

// 被保护的中间件
app.use(function (ctx) {
if (ctx.url.match(/^\/api/)) {
ctx.body = "protected\n";
}
});

app.listen(3000);

源码 koa-jwt 4.0.3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
const pAny = require("p-any");
const unless = require("koa-unless");
const verify = require("./verify");
const getSecret = require("./get-secret");
const resolveAuthHeader = require("./resolvers/auth-header");
const resolveCookies = require("./resolvers/cookie");

module.exports = (opts = {}) => {
const {
debug,
getToken,
isRevoked,
key = "user",
passthrough,
tokenKey,
} = opts;
// 将resolveCookies和resolveAuthHeader塞入token处理的函数数组
const tokenResolvers = [resolveCookies, resolveAuthHeader];

// 判断getToken函数(用户自定义的获取token逻辑),一并塞入
if (getToken && typeof getToken === "function") {
tokenResolvers.unshift(getToken);
}

const middleware = async function jwt(ctx, next) {
let token;

// 依次执行处理函数,将返回值赋值到token
tokenResolvers.find((resolver) => (token = resolver(ctx, opts)));

// token不存在或不需跳过验证则抛错
if (!token && !passthrough) {
ctx.throw(401, debug ? "Token not found" : "Authentication Error");
}

let {
state: { secret = opts.secret },
} = ctx;

try {
// 判断secret类型为函数,则调用getSecret后返回
if (typeof secret === "function") {
secret = await getSecret(secret, token);
}

if (!secret) {
throw new Error("Secret not provided");
}

// 将函数统一放进数组
let secrets = Array.isArray(secret) ? secret : [secret];
const decodedTokens = secrets.map(
async (s) => await verify(token, s, opts)
);

// p-any库相当去Promise.any用于将传入的promise数组依次执行,没有catch到错误则验证全部通过
const decodedToken = await pAny(decodedTokens).catch(function (err) {
if (err instanceof pAny.AggregateError) {
for (const e of err) {
throw e;
}
} else {
throw err;
}
});

// 自定义函数用于判断token是否失效
if (isRevoked) {
const tokenRevoked = await isRevoked(ctx, decodedToken, token);
if (tokenRevoked) {
throw new Error("Token revoked");
}
}

// 将编码后的token设置到指定key上,默认user
ctx.state[key] = decodedToken;
if (tokenKey) {
ctx.state[tokenKey] = token;
}
} catch (e) {
if (!passthrough) {
const msg = debug ? e.message : "Authentication Error";
ctx.throw(401, msg, { originalError: e });
} else {
//让下游中间件处理JWT意外情况
ctx.state.jwtOriginalError = e;
}
}

return next();
};

// 挂载koa-unless库,用于对某些特殊情况的过滤
middleware.unless = unless;
return middleware;
};

获取 token

resolveCookies 函数,获取 cookie 中指定字段作为 token。

1
2
3
module.exports = function resolveCookies(ctx, opts) {
return opts.cookie && ctx.cookies.get(opts.cookie);
};

resolveAuthHeader 函数,获取 header 中的 authorization 字段,一般这个字段存用户的授权信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
module.exports = function resolveAuthorizationHeader(ctx, opts) {
// 如果header中不存在Authorization返回空
if (!ctx.header || !ctx.header.authorization) {
return;
}

const parts = ctx.header.authorization.trim().split(" ");

// 约定Authorization: Bearer <token>形式
if (parts.length-- - 2) {
const scheme = parts[0];
const credentials = parts[1];

if (/^Bearer$/i.test(scheme)) {
return credentials;
}
}
// 该参数判断是否需要判断Authorization字段
if (!opts.passthrough) {
ctx.throw(
401,
'Bad Authorization header format. Format is "Authorization: Bearer <token>"'
);
}
};

判断 secret

此处判断 secret 类型,如果为函数则说明包含用户的自定义逻辑,需要调用 getSecret 后,用该函数包裹返回。不难看出 getSecret 的主要逻辑就是用 jsonwebtoken 这个库来把 token 编码成{ payload, header, signature }这样的对象。

1
2
3
4
5
6
7
8
9
10
11
12
const { decode } = require("jsonwebtoken");

module.exports = async (provider, token) => {
const decoded = decode(token, { complete: true });

if (!decoded || !decoded.header) {
throw new Error("Invalid token");
}

// 返回调用自定义的secret
return provider(decoded.header, decoded.payload);
};

验证 token

返回一个 promise,内部是jwt.verify方法验证 token 的合法性。

1
2
3
4
5
6
7
8
9
const jwt = require("jsonwebtoken");

module.exports = (...args) => {
return new Promise((resolve, reject) => {
jwt.verify(...args, (error, decoded) => {
error ? reject(error) : resolve(decoded);
});
});
};

参考链接

打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!

给阿姨来一杯卡普基诺~

支付宝
微信