我们目前已经实现了路由
和代码自动格式化
,那么让我们来继续学一学路由鉴权。
一、为什么要路由授权?
很显然,一台服务器上的资源不可能对所有人开放;
我们的一个路由,就对应了一种类型的资源。
以下场景:
- 未登录的人员,不能访问注册会员才能查看的信息;
- 降低系统压力,仅为授权人员服务;
二、常见授权方式
那么,就趁此机会,让我们来学学现在常见的授权方式都有哪些吧!
2.1 OAuth2.0
比较流行的授权方式,主要是用于授权第三方应用,获取用户数据;
比如你的应用使用微信登录,会提示获取你是否确认授权三方获取用户信息(OPENID
、头像)等等,点击允许后就会拿上票据跳转到三方,三方换取到 access_token
后,就能拿到了微信提供的用户信息,官方时序图如下:
具体可见:微信OAuth2.0对接介绍
PS:大部分应用首次还会让你再输入手机号+短信验证码,做手机号与微信号的绑定。
由于咱们只是了解,更多可见阮老师的OAuth2.0的简单解释
2.2 CAS
CAS
与 OAuth2.0
类似,也是做身份验证和授权,但是 CAS
主要用于单点登录( SSO
:即登录一次即可访问所有与该系统连接的应用)。
流程图如下:
想必大家也遇到过,使用 A
系统先跳转到统一认证中心,输入完密码后再跳转回 A
系统。就是这样的机制啦。(更往上就是 IAM
系统,这里先按住不表)
2.3 JWT
JWT
即 JSON WEB TOKEN
,不难理解是以 JSON
格式进行 WEB
认证的 Token
。
看一下官方介绍:
JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在网络应用间传输信息的一种方式。
具有无状态、有效避免 CSRF 攻击、使用移动端、单点登录友好等特点。
通常,JWT
多用于跨域认证和授权,也是一种比较简单的机制。
由三部分构成:
- Header:元数据,定义生成签名算法及Token类型;
- Payload:存放需要传递的数据;
- Signature:服务器使用
Payload + Header + Secret
通过Header
中指定的算法生成。
一般形如:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImZlbmd5cyIsImlhdCI6MTY4NDMwMjk0MSwiZXhwIjoxNjg0Mzg5MzQxfQ.n7DZdoTSW6O0wD0VclFIE0MEwK1tkyTiXSUeX4ZkgFI
OK,let’s do it!
三、整体流程
3.1 接口区分
首先,我们需要对路由进行区分,哪些是开放路由,哪些是需要鉴权成功(输入用户名密码)后获取 JWT
,请求头中带上才能使用的路由。
也就是分为两种角色:
- 无需登录使用;(游客)
- 需要登录后使用;(已登录用户)
3.2 流程图
四、使用 KOA-JWT
该组件就是一个在 KOA
中的 JWT
中间件。
4.1 安装
npm install koa-jwt jsonwebtoken
- koa-jwt:对路由进行拦截判断
- jsonwebtoken:生成
JWT
4.2 使用
关键代码
const jwt = require("koa-jwt");
// 类似于加 salt,需要传递一个密钥串
const JWT_SECRET = "shared-secret"
// 生成 JWT
jwt.sign({ id: uId }, JWT_SECRET, { expiresIn: 24 * 60 * 60 })
/.../
// 给路由加上了 `JWT` 校验,使用 `unless` 去排查不需要校验的路由
app.use(jwt({ secret: JWT_SECRET }).unless({ path: [/^\/auth/] }));
然后,我们来设计一个 auth
接口,用于获取认证信息,当用户名密码输入正确(暂未涉及数据库,用固定参数)后才返回;
设计一个 user
接口,用户返回用户信息,该接口需要授权成功后才能访问,否则就报未授权错误。
完整如下:
const koa = require("koa");
const Router = require("@koa/router");
const jwt = require("jsonwebtoken");
const koaJWT = require("koa-jwt");
const { koaBody } = require("koa-body");
const app = new koa();
const router = new Router();
const JWT_SECRET = "shared-secret";
/**
* 授权接口,用户名密码正确返回 JWT ,错误则提示
*/
router.post("/auth", async (ctx, next) => {
const jwtString = jwt.sign({ id: "root" }, JWT_SECRET, {
expiresIn: 24 * 60 * 60
});
const { uId, password } = ctx.request.body;
if (uId === "root" && password === "123456") {
ctx.body = { code: "200", msg: "success", token: jwtString };
} else {
ctx.body = { code: "-1", msg: "The user name or password is incorrect" };
}
});
/**
* 获取用户信息接口,需携带 JWT 访问
*/
router.get("/user", async (ctx, next) => {
ctx.body = "This is user info.";
});
app.use(koaBody());
app.use(koaJWT({ secret: "shared-secret" }).unless({ path: [/^\/auth/] }));
app.use(router.routes());
app.listen(3000, () => {
console.log("启动成功!http://localhost:3000");
});
验证:
不携带 JWT
访问未授权接口 localhost:3000/user
,提示错误:
调用localhost:3000/user
获取 JWT
:
密码错误情况:
正确:
携带 JWT
访问未授权接口 localhost:3000/user
,返回用户信息:
注意:使用时要在前面增加 Bearer
,为什么要这样加?不能不加?就留给大家去探索了!