路由组件(生成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