react+koa+vite前后端模拟jwt鉴权过程

路由组件(生成token)

const Router = require('@koa/router')
const jwt = require('jsonwebtoken');
const router = new Router()

const mockDbUserInfo = [
  {
    nickname: 'xxxliu',
    username: 'Tom',
    password: 123456,
    icon: 'url1'
  },
  {
    nickname: 'xxx',
    username: 'John',
    password: 123456,
    icon: 'url2'
  },
]

const secretKey = 'xxxliu key';

router.post('/api/home', async ctx => {
  ctx.response.body = {
    msg: '主页',
    code: 'success'
  }
})


router.post('/api/login', async ctx => {
  try {
    const { username, password } = ctx.request.body;
    //mock查db操作
    const { nickname, password: pwd, icon } = mockDbUserInfo.find(item => item.username === username) || {};

    if (!nickname) {
      ctx.response.body = {
        msg: "不存在该用户",
        code: "failed",
      };
      return;
    }

    if (pwd !== password) {
      ctx.response.body = {
        msg: "密码输入错误",
        code: "error",
      };
      return;
    }

    // 构建 JWT 头部
    const header = {
      alg: 'HS256', // 签名算法,例如使用 HMAC SHA-256
      typ: 'JWT', // Token 的类型
    };
    const payload = {
      username
    };
    const options = {
      expiresIn: '1h', // 设置 JWT 的过期时间
      header, // 将 header 选项包含在 options 中
    };

    //生成token
    const token = jwt.sign(payload, secretKey, options);

    ctx.response.body = {
      nickname,
      icon,
      code: "success",
      msg: "登录成功",
    };

    ctx.cookies.set(
      'myToken',
      token,
      {                
        maxAge: 1 * 60 * 60 * 1000,       
        httpOnly: false
      }
    )

  } catch (error) {
    ctx.response.body = error;
  }
})

module.exports = router;

token解析

const jwt = require('jsonwebtoken');

const secretKey = 'xxxliu key';
const verifyToken = async (ctx, next) => {
  try {
    const { url } = ctx.request;

    //走登录页不鉴权
    const requestUrl = url.split("?")[0];
    const noVerifyList = ["/api/login"];
    const noVerify = noVerifyList.includes(requestUrl);

    if (noVerify) {
      await next();
    } else {
      //拿到请求头的参数
      const authorization = ctx.request.header["authorization"];
      const username = ctx.request.body["username"];

      if (authorization.startsWith('Bearer ')) {
        const token = authorization.slice(7);
        const { exp, username: userName } = jwt.verify(token, secretKey) || {};

        if (userName !== username) {
          ctx.response.body = {
            code: "error",
            msg: "无效的token, 请重新登录",
          };
        }
        else if (exp * 1000 > Date.now()) {
          ctx.response.body = {
            code: "error",
            msg: "登录信息已过期, 请重新登录",
          };
        } else {
          await next();
        }
      } else {
        ctx.response.body = {
          code: "error",
          msg: "无效的token, 请重新登录",
        };
      }
    }
  } catch (err) {
    if (err.name == "TokenExpiredError") {
      ctx.body = {
        code: "error",
        msg: "token已过期, 请重新登录",
      };
    } else if (err.name == "JsonWebTokenError") {
      ctx.body = {
        code: "error",
        msg: "无效的token, 请重新登录",
      };
    }
  }

};

module.exports = verifyToken;

注册中间件

const Koa = require('koa')
const path = require('path')
const sendfile = require('koa-sendfile')
const static = require('koa-static')
const bodyParser = require("koa-bodyparser")
const router = require('./server/api-router.js')
const assets = require('./server/assets-router')
const verifyToken = require('./server/verifyToken.js');
const app = new Koa()

// static
app.use(static(path.resolve(__dirname, 'public')))

//body
app.use(bodyParser());

//midware auth
app.use(verifyToken);

// api
app.use(router.routes()).use(router.allowedMethods())

// assets
app.use(assets.routes()).use(assets.allowedMethods())

// 404
app.use(async (ctx, next) => {
  await next()

  if (ctx.status === 404) {
    await sendfile(ctx, path.resolve(__dirname, 'public/index.html'))
  }
})

app.listen(3000, () => {
  console.log(`> http://127.0.0.1:3000`)
})

前端请求(登录+打开主页)

import { useState, useEffect } from 'react'
import getTokenFromCookie from './tools'
function App() {
  const [response, setResponse] = useState({})

  const token = getTokenFromCookie();

  useEffect(() => {
    fetch('/api/login', {
      method: 'post',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${token}`,
      },
      body: JSON.stringify({
        username: 'Tom',
        password: 123456
      })
    })
      .then(res => res.json())
      .then(res => {
        setResponse(res);
      })

    // fetch('/api/home', {
    //   method: 'post',
    //   headers: {
    //     'Content-Type': 'application/json',
    //     'Authorization': `Bearer ${token}`,
    //   },
    //   body: JSON.stringify({
    //     username: 'Tom',
    //     password: 123456
    //   })
    // })
    //   .then(res => res.json())
    //   .then(res => {
    //     setResponse(res);
    //   })

  }, [])

  const { nickname, icon, msg } = response || {}

  return (
    <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
      {
        nickname ? <div>
          <h1>icon: {icon}</h1>
          <h1>nickname: {nickname}</h1>
          <h1>msg: {msg}</h1>
        </div>
          : <h1>msg: {msg}</h1>
      }
    </div>
  )
}

export default App

你可能感兴趣的:(react.js,前端,node.js,jwt,koa)