登录鉴权

创建JWT签名密钥

这个密钥在生成和校验 token 都要用到,建议配置到环境变量中。

# .env
JWT_SECRET="my_first_secret"

koa-jwt

该模块允许您在Koa程序中使用 JSON Web 令牌验证 HTTP 请求,在以后的每一次请求中它都会去校验你令牌的合法性。

import KoaJwt from "koa-jwt";

// 给路由加上了 `JWT` 校验,并使用 `unless` 去排查不需要校验的路由(注意:放在路由前面)
const koaJwt = KoaJwt({
  secret: process.env.JWT_SECRET
}).unless({
  path:[/^\/login/, /^\/register/]
})
app.use(koaJwt);

// 正常路由定义
app.use(router());

如上定义的接口,在请求的时候必须带上令牌信息,否则报 401

// 客户端请求
// 使用时要在前面增加 Bearer,为什么要这样加?不能不加?就留给大家去探索了!
axios.get('/xxx',{
    headers: {
        Authorization: 'Bearer xxx' // 这里的 xxx 就是 token,是由后端给的(登录成功后存到本地中)
    }
})

jsonwebtoken

jsonwebtoken就是 jwt,用于(给前端)产出 令牌(token)!

生成令牌

import jwt from "jsonwebtoken";

// 生成唯一令牌
const genToken = (payload) => {
    const params = { expiresIn: '1h' };
    const token = jwt.sign(payload, process.env.JWT_SECRET, params);
    return token;
}
// 参数意义:
// payload:载体,一般把用户信息作为载体来生成token
// secret:秘钥,可以是字符串也可以是文件
// expiresIn:过期时间 1h表示一小时

一般会在登录成功之后执行此操作,

// 登录
router.post("/login", async (ctx, next) => {
  const user = await prisma.user.findFirst({ where: ctx.request.body });
  if(user){
    ctx.body = JsonResult.success({
      token: genToken(user)
    })
  }else{
    ctx.body = JsonResult.failed("暂未注册,请先注册!");
  }
})

解密令牌

jwt.verify(来自 jsonwebtoken 库)和 koa-jwt 的 Token 验证本质上是相同的,但它们的应用场景和灵活性不同。

koa-jwt 是一个 Koa 中间件,会自动检查请求头中的 Authorization,如果 Token 无效(签名错误、过期等),直接返回 401 Unauthorized,并终止请求。

jwt.verify 是 手动验证 Token 的底层方法,通常在以下场景使用:需要自定义错误处理(而非直接 401)、需要获取 Token 的解码内容、非 HTTP 场景的验证

import jwt from "jsonwebtoken";

try {
  const decoded = jwt.verify(token, 'wrong-secret');
  console.log('令牌有效,解码后内容为', decoded)
} catch(err) {
  console.log('令牌无效,错误为', err)
}

自定义401

如果你不想给用户返回 401 错误,可以通过中间件的方式进行拦截处理

// Custom 401 handling if you don't want to expose koa-jwt errors to users
app.use(function(ctx, next){
  return next().catch((err) => {
    if (401 == err.status) {
      ctx.status = 401;
      ctx.body = 'Protected resource, use Authorization header to get access\n';
    } else {
      throw err;
    }
  });
});

挂在全局

在 Koa 应用中使用 koa-jwt 中间件时,默认情况下它会验证 JWT 的有效性,但不会自动将解析后的 payload 挂载到 ctx 上。如果你需要在后续中间件或路由中访问 JWT 的 payload(例如用户信息),可以通过自定义中间件来实现。在 koa-jwt 之后添加一个中间件,手动解析并挂载 payload 到 ctx.state.user(或其他自定义属性):

const Koa = require('koa');
const Router = require('koa-router');
const jwt = require('jsonwebtoken');
const koaJwt = require('koa-jwt');

const app = new Koa();
const router = new Router();

// 1. 使用 koa-jwt 中间件(验证 JWT)
app.use(koaJwt({
  secret: process.env.JWT_SECRET,
}).unless({
  path: [/^\/login/, /^\/register/]
}));

// 2. 自定义中间件:解析 token 并挂载 payload 到 ctx
app.use(async (ctx, next) => {
  if (ctx.header && ctx.header.authorization) {
    const token = ctx.header.authorization.replace('Bearer ', '');
    try {
      const payload = jwt.verify(token, process.env.JWT_SECRET);
      ctx.state.user = payload; // 挂载到 ctx.state.user
      // 或者挂载到其他属性,如 ctx.user
    } catch (err) {
      // 如果 token 解析失败,可以忽略(因为 koa-jwt 已经验证过有效性)
      console.error('Token 解析失败:', err);
    }
  }
  await next();
});

// 3. 路由示例:访问挂载的 payload
router.get('/protected', async (ctx) => {
  // 从 ctx.state.user 获取 payload
  ctx.body = {
    message: '访问受保护的路由',
    user: ctx.state.user // 这里可以拿到 JWT 的 payload
  };
});

app.use(router.routes());
app.listen(3000);

--==封装==--

你还可以将第二步封装为一个中间件

// middleware/user-mount.ts
const userMount = async (ctx, next) => {
    if (ctx.header?.authorization) {
        const token = ctx.header.authorization.replace('Bearer ', '');
        try {
            ctx.state.user = jwt.verify(token, process.env.JWT_SECRET);
        } catch (err) {
            console.error('Token 解析失败:', err);
        }
    }
    await next();
}

// app.ts
app.use(userMount);

其他

明文密码

密码建议不要明文入库,可以使用 node 自带的 crypto 模块进行加密

jwt原则

koa-jwt 验证 Token 的有效性,本质上是 验证 JWT 的签名是否由合法的 secret(或密钥)生成,同时会检查 Token 的结构、有效期等标准字段。

重启程序导致token失效

那是因为咱们之前生成的 token都存在后端项目的变量中,而变量是基于内存的,要想 token 不受限于程序的启停,建议把 token 存储到 no-sql 数据库中,作为缓存,比如 redis可以缓存 token,并设置过期时长。