114.JWT(json web token)实现权限认证过程
JWT(json web token)实现权限认证过程
b站跟着哈默的视频一起敲的代码,和视频里面内容不一样的地方
- 视频略过了项目搭建的过程
- 视频中使用的是选项式api,我用的是组合式api(顺便实战一下vue3的编程风格)
最终的实现效果:
制作登录页面实现获取token,登录成功后使用jsonwebtoken
验证token
有效性,验证成功之后跳转到list页面,在List页面做权限用户控制,获取信息接口不做权限控制,新增接口做权限控制。
使用js-base64
对token进行加密,服务端使用basic-auth
验证token。
使用element3+vue3搭建客户端框架:
npm install vue@next 安装最新版vue
npm i -g @vue/cli 安装最新版本vue-cli
vue create jwt-client 创建客户端项目
npm install element-plus --save 使用element-plus
main.js中
import { createApp } from "vue";
//引入elementplus
import ElementPlus from "element-plus";
import "element-plus/dist/index.css";
import App from "./App.vue";
const app = createApp(App);
app.use(ElementPlus).mount("#app");
npm install babel-eslint 解决 eslint 的 Parsing error: Unexpected token 错误
@vue/cli-plugin-babel 解决vue文件template报错问题
npm install axios
npm install vue-router@4
import router from "./router/index";
const app = createApp(App);
app.use(ElementPlus).use(router).mount("#app");
npm install js-base64
新建views文件夹,新建login.vue文件,实现登录页面,login.vue
登录成功后,token接口返回token信息,前端将token存在localStorage中,登录成功后跳转到列表页
<template>
<el-form :model="form" label-width="80px" ref="ruleFormRef">
<el-form-item label="用户名" prop="username">
<el-input v-model="form.username" />
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="form.password" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit(ruleFormRef)">登录</el-button>
<el-button type="primary" @click="resetForm(ruleFormRef)">重置</el-button>
</el-form-item>
</el-form>
</template>
<script setup>
import { reactive, ref } from "vue";
import axios from "axios";
import { useRouter } from "vue-router";
const { push } = useRouter();
//设置ref
const ruleFormRef = ref();
//设置响应式数据
const form = reactive({
username: "",
password: "",
});
// 提交表单
const onSubmit = (formEl) => {
if (!formEl) return;
console.log(formEl, form);
formEl.validate((valid) => {
if (valid) {
//默认用得get请求,请求方式不对请求不到
axios
.post("/token", {
username: form.username,
password: form.password,
})
.then((res) => {
const result = res.data;
if (result.status) {
localStorage.setItem("token", result.token);
verifyToken();
}
console.log(res);
});
} else {
return false;
}
});
};
//重置表单
const resetForm = (formEl) => {
if (!formEl) return;
formEl.resetFields();
};
//验证token有效性
const verifyToken = () => {
axios
.post("/verify", {
token: localStorage.getItem("token"),
})
.then((res) => {
debugger;
const result = res.data;
if (result.isValid) {
//路由跳转
// this.$router.push("/list");
push({
path: "/list",
});
ElMessage({
message: "登录成功",
type: "success",
});
}
});
};
</script>
列表页,使用按钮模拟权限,获取文章内容不需要用户权限控制,新增文章内容需要进行用户权限控制。
<template>
<div class="home">
<el-button @click="getContent">获取文章内容</el-button>
<el-button @click="addContent">新增文章内容</el-button>
</div>
</template>
<script setup>
import axios from "axios";
import { ElMessage } from "element-plus";
import { Base64 } from "js-base64";
const getContent = () => {
axios.get("/content").then((res) => {
console.log(res);
ElMessage({
message: res.data.msg,
type: "success",
});
});
};
const addContent = () => {
axios({
url: "/content",
method: "post",
headers: {
Authorization: _encode(),
},
}).then((res) => {
if (res.data.status) {
ElMessage({
message: res.data.msg,
type: "success",
});
} else {
ElMessage({
message: res.data.msg,
type: "error",
});
}
});
};
//使用base64加密token
const _encode = () => {
const token = localStorage.getItem("token");
const encoded = Base64.encode(`${token}:`);
return `Basic ${encoded}`;
};
</script>
配置vue.config.js中proxy代理访问5000端口数据
使用koa+koa-router搭建服务端:
新建jwt-server目录npm init 初始化package.json
npm install koa 创建服务器
npm install koa-bodyparser 解析请求中的body中的数据
npm i @koa/router --save
npm install jsonwebtoken
npm install basic-auth
新建app.js
const Koa = require("koa");
const bodyParser = require("koa-bodyparser");
const app = new Koa();
//获取Body内容需要使用Bodyparser
//开启一个后端服务,使用5000端口
app
.use(bodyParser())
.listen(5000);
实现获取token接口,使用koa-router模拟请求路径,最后导出router,在app.js中引入,并且使用
新建api文件夹,新建token.js
//使用@koa/router生成访问路径
const Router = require("@koa/router");
//模拟用户信息
const users = [
{ id: 1, username: "zhangsan", password: 123456, nickname: "张三" },
{ id: 2, username: "lisi", password: 123456, nickname: "李斯" },
];
//生成token信息
const { generatorToken } = require("../../core/utils");
//验证token信息得类
const Auth = require("../../middlewares/auth");
const router = new Router();
router.post("/token", async (ctx, next) => {
//从body中获取传参
const { username, password } = ctx.request.body;
const token = verifyUsernamePassword({ username, password });
if (!token) {
ctx.body = {
errCode: 10001,
msg: "用户名或密码不正确",
request: `${ctx.method} ${ctx.path}`,
status: false,
};
return;
}
ctx.body = {
token,
status: true,
};
});
//验证token有效性
router.post("/verify", async (ctx) => {
const token = ctx.request.body.token;
const isValid = verifyToken(token);
ctx.body = {
isValid,
};
});
//验证用户名和密码
function verifyUsernamePassword(user) {
//在此可以定义后端返回得数据格式
const index = users.findIndex((item) => {
return item.username === user.username && item.password == user.password;
});
const userInfo = users[index];
if (!userInfo) {
return undefined;
}
//2:user权限数字
const token = generatorToken(user.id, Auth.USER);
return token;
}
//验证token
function verifyToken(token) {
return Auth.verifyToken(token);
}
module.exports = router;
在app.js中使用router
const Koa = require("koa");
const bodyParser = require("koa-bodyparser");
const tokenRouter = require("./app/api/token");
const app = new Koa();
//获取Body内容需要使用Bodyparser
app
.use(bodyParser())
.use(tokenRouter.routes())
.listen(5000);
utils.js中generatorToken方法用来生成token
const jwt = require("jsonwebtoken");
const { secretKey, expiresIn } = require("../config/config");
//生成token令牌
const generatorToken = function (uuid, scope) {
const token = jwt.sign({ uuid, scope }, secretKey, { expiresIn });
return token;
};
module.exports = { generatorToken };
中间件auth.js,验证token信息以及进行权限认证相关逻辑
const jwt = require("jsonwebtoken");
const { secretKey } = require("../config/config");
class Auth {
static verifyToken(token) {
try {
jwt.verify(token, secretKey);
return true;
} catch (error) {
return false;
}
}
}
module.exports = Auth;
通用配置config.js,生成令牌和验证时使用
module.exports = {
secretKey: "jK123uu_s$!",
expiresIn: 24 * 60 * 60,
};
内容相关api
const Auth = require("../../middlewares/auth");
const Router = require("@koa/router");
const router = new Router();
router.get("/content", (ctx) => {
ctx.body = {
result: "获取内容成功",
msg: "操作成功",
status: true,
};
});
router.post("/content", new Auth(5).middleware, (ctx) => {
ctx.body = {
result: "新增内容成功",
msg: "操作成功",
status: true,
};
});
module.exports = router;
middleware方法
const jwt = require("jsonwebtoken");
const { secretKey } = require("../config/config");
var auth = require("basic-auth");
class Auth {
constructor(level) {
Auth.USER = 2;
Auth.ADMIN = 8;
this.level = level;
}
get middleware() {
return async (ctx, next) => {
var token = auth(ctx.request);
let errMsg = "token不合法";
//token不存在
if (!token || token.name === "null") {
ctx.body = {
errCode: 10005,
msg: errMsg,
request: `${ctx.body} ${ctx.path}`,
status: false,
};
return;
}
try {
//验证token
var decoded = jwt.verify(token.name, secretKey);
} catch (e) {
//token过期
if (e.name === "tokenExpiredError") {
errMsg = "token已过期";
}
ctx.body = {
errCode: 10005,
msg: errMsg,
request: `${ctx.body} ${ctx.path}`,
};
return;
}
//判断用户权限
if (decoded.scope < this.level) {
errMsg = "没有权限";
ctx.body = {
errCode: 10005,
msg: errMsg,
request: `${ctx.body} ${ctx.path}`,
};
return;
}
await next();
};
}
static verifyToken(token) {
try {
jwt.verify(token, secretKey);
return true;
} catch (error) {
return false;
}
}
}
module.exports = Auth;