# 1. 前言
在一个基于 RESTful
规范的应用中,URL
对应的不同路径意味着不同的资源,所以对资源访问的限制即对 URL
访问的限制。常见鉴别用户权限的方式有两种,一种是广泛使用的 Cookie-Based Authentication
( 基于 Cookie
的认证模式),另一种是 Token-Based-Authentication
(基于 Token
的认证模式)。在本篇文章中我们以基于 Token
的认证方式为例来介绍如何对接口进行权限控制。
Token
方式最大的优点在于采用了无状态机制,在此基础上,可以实现天然的跨域支持、前后端分离等,同时降低了服务端开发和维护的成本。Token
方式的缺点在于服务器每次都需要对 Token
进行校验,这一步骤会对服务器产生运算压力。另一方面,无状态 API
缺乏对用户流程或异常的控制,为了避免出现一些例如回放攻击的异常情况,应该设置较短的过期时间,且需要对密钥进行严格的保护。对于具有复杂流程的高危场景(如支付等),则要谨慎选择 Token
认证模式。
# 2. Token 认证流程
基于 Token
的前后端互相认证流程通常是这样子的:
- 客户端访问
login
登录接口请求登录; - 服务端校验客户端请求携带的参数是否合法,如果合法则以接口返回值形式向客户端颁发
Token
; - 客户端拿到
Token
后,在之后的请求需要在请求头中的Authorization
字段中携带Token
; - 之后服务端在接收到请求后,会首先根据请求携带的
Token
进行认证,如果认证通过,则进行下一步操作,否则,拒绝访问。
其交互流程图如下:
# 3. 结合 koa 实现 Token 认证流程
在 koa
中,通常情况下,一般会选用 jsonwebtoken
( JWT
)这个第三方工具库来实现 Token
的生成、校验和解码。而 Token
的中间件实现会选择 koa-jwt
。koa-jwt
会在中间件流程中通过 JWT
完成对 Token
的校验和解码,开发者只需通过 JWT
来实现 Token
的生成即可。
老规矩,使用之前我们先来安装一下这两个库,如下:
# 安装 jsonwebtoken koa-jwt
npm i jsonwebtoken koa-jwt
# 安装 jsonwebtoken 的类型文件
npm i @types/jsonwebtoken -D
2
3
4
5
安装完成之后,我们就可以来使用了。
首先,我们需要使用 koa-jwt
来生成一个 Token
校验中间件,为了方便复用,我们在 src/middleware
目录下创建一个名为 jwt.ts
文件,写入如下代码:
import * as JWT from "koa-jwt";
import { JWT_SCERET } from "../config";
export default JWT({ secret: JWT_SCERET });
2
3
4
从上面代码中可以看到,我们先在 src/config/index.ts
文件中定义了 JWT_SCERET
变量,用来保存 jwt
的密码。然后调用 koa-jwt
中的 JWT
方法来生成 JWT
中间件。
接下来,我们创建一条登录路由,在 src/router
目录下创建 login.ts
文件,并写入如下内容:
import * as Koa from "koa";
import * as Router from "koa-router";
import LoginController from "../controller/login";
const router = new Router();
export default (app: Koa) => {
router.post("/login", LoginController.login);
app.use(router.routes()).use(router.allowedMethods());
};
2
3
4
5
6
7
8
9
10
11
接着,我们继续实现 LoginController.login
,在 src/controller
目录下创建 login.ts
文件,并写入如下内容:
import * as Koa from "koa";
import { sign } from "jsonwebtoken";
import { JWT_SCERET } from "../config";
import UserService from "../service/user";
export default {
login: async (ctx: Koa.Context, next: Koa.Next) => {
const { name } = ctx.request.body;
// 校验登录是否合法
const res = await UserService.findUserByName(name);
if (res.data) {
// 如果合法,则生成token 并颁发 token
const token = sign({ name }, JWT_SCERET, {
expiresIn: "300s", // token 过期时间为 5 分钟
});
ctx.body = {
msg: "登录成功",
token,
};
} else {
ctx.body = {
msg: "登录失败,用户名不正确",
};
}
},
};
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
从上面代码中可以看到,当客户端登录请求过来后,首先判断请求所携带的参数是否合法(一般会校验登录的用户名和密码是否正确,此处为了简便起见,只校验该用户是否存在于数据库中),如果合法,则根据用户信息生成 Token
并通过返回值颁发给客户端;如果不合法,则返回登录失败信息。
ok,接下来启动服务器,我们就可以尝试发送登录请求来获取 Token
了,打开 postman
,如下:
可以看到,当携带正确的用户名 nlrx
进行登录时, Token
已经被正确返回了。如果用户名错误,则提示登录失败。
接下来,我们给需要进行权限控制的接口添加上前面生成好的 Token
校验中间件,以 /user
为例,如下:
// src/router/user.ts
import * as Koa from "koa";
import * as Router from "koa-router";
import UserController from "../controller/user";
import jwt from "../middleware/jwt";
const router = new Router();
export default (app: Koa) => {
router.post("/user", jwt, UserController.createUser);
router.put("/user/:name", jwt, UserController.updateUser);
router.del("/user/:name", jwt, UserController.deleteUser);
router.get("/user/:name", jwt, UserController.getUser);
app.use(router.routes()).use(router.allowedMethods());
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
加上之后,我们保存文件再次重启服务器,此时如果再向之前那样直接访问接口的话,此时接口应该会访问不通,如下:
可以看到,返回了 Authentication Error
即「授权失败」。
那么,我们携带上 Token
访问,再看看:
可以看到,当我们在请求的 Headers
中添加上 Authorization
字段,并把字段值设置为 Bearer <Token>
后,即可返回正确的结果。当没有 Token
或 Token
过期时,都会提示授权失败。
以上,我们就实现了给接口添加访问权限控制的功能。