
JWT(json web token)實(shí)現(xiàn)權(quán)限認(rèn)證過(guò)程
b站跟著哈默的視頻一起敲的代碼,和視頻里面內(nèi)容不一樣的地方
- 視頻略過(guò)了項(xiàng)目搭建的過(guò)程
- 視頻中使用的是選項(xiàng)式api,我用的是組合式api(順便實(shí)戰(zhàn)一下vue3的編程風(fēng)格)
最終的實(shí)現(xiàn)效果:
制作登錄頁(yè)面實(shí)現(xiàn)獲取token,登錄成功后使用jsonwebtoken驗(yàn)證token有效性,驗(yàn)證成功之后跳轉(zhuǎn)到list頁(yè)面,在List頁(yè)面做權(quán)限用戶(hù)控制,獲取信息接口不做權(quán)限控制,新增接口做權(quán)限控制。
使用js-base64對(duì)token進(jìn)行加密,服務(wù)端使用basic-auth驗(yàn)證token。
使用element3+vue3搭建客戶(hù)端框架:
npm install vue@next 安裝最新版vue
npm i -g @vue/cli 安裝最新版本vue-cli
vue create jwt-client 創(chuàng)建客戶(hù)端項(xiàng)目
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 錯(cuò)誤
@vue/cli-plugin-babel 解決vue文件template報(bào)錯(cuò)問(wèn)題
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文件,實(shí)現(xiàn)登錄頁(yè)面,login.vue
登錄成功后,token接口返回token信息,前端將token存在localStorage中,登錄成功后跳轉(zhuǎn)到列表頁(yè)
<template>
<el-form :model="form" label-width="80px" ref="ruleFormRef">
<el-form-item label="用戶(hù)名" 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();
//設(shè)置ref
const ruleFormRef = ref();
//設(shè)置響應(yīng)式數(shù)據(jù)
const form = reactive({
username: "",
password: "",
});
// 提交表單
const onSubmit = (formEl) => {
if (!formEl) return;
console.log(formEl, form);
formEl.validate((valid) => {
if (valid) {
//默認(rèn)用得get請(qǐng)求,請(qǐng)求方式不對(duì)請(qǐng)求不到
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();
};
//驗(yàn)證token有效性
const verifyToken = () => {
axios
.post("/verify", {
token: localStorage.getItem("token"),
})
.then((res) => {
debugger;
const result = res.data;
if (result.isValid) {
//路由跳轉(zhuǎn)
// this.$router.push("/list");
push({
path: "/list",
});
ElMessage({
message: "登錄成功",
type: "success",
});
}
});
};
</script>
列表頁(yè),使用按鈕模擬權(quán)限,獲取文章內(nèi)容不需要用戶(hù)權(quán)限控制,新增文章內(nèi)容需要進(jìn)行用戶(hù)權(quán)限控制。
<template>
<div class="home">
<el-button @click="getContent">獲取文章內(nèi)容</el-button>
<el-button @click="addContent">新增文章內(nèi)容</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代理訪(fǎng)問(wèn)5000端口數(shù)據(jù)
使用koa+koa-router搭建服務(wù)端:
新建jwt-server目錄npm init 初始化package.json
npm install koa 創(chuàng)建服務(wù)器
npm install koa-bodyparser 解析請(qǐng)求中的body中的數(shù)據(jù)
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內(nèi)容需要使用Bodyparser
//開(kāi)啟一個(gè)后端服務(wù),使用5000端口
app
.use(bodyParser())
.listen(5000);
實(shí)現(xiàn)獲取token接口,使用koa-router模擬請(qǐng)求路徑,最后導(dǎo)出router,在app.js中引入,并且使用
新建api文件夾,新建token.js
//使用@koa/router生成訪(fǎng)問(wèn)路徑
const Router = require("@koa/router");
//模擬用戶(hù)信息
const users = [
{ id: 1, username: "zhangsan", password: 123456, nickname: "張三" },
{ id: 2, username: "lisi", password: 123456, nickname: "李斯" },
];
//生成token信息
const { generatorToken } = require("../../core/utils");
//驗(yàn)證token信息得類(lèi)
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: "用戶(hù)名或密碼不正確",
request: `${ctx.method} ${ctx.path}`,
status: false,
};
return;
}
ctx.body = {
token,
status: true,
};
});
//驗(yàn)證token有效性
router.post("/verify", async (ctx) => {
const token = ctx.request.body.token;
const isValid = verifyToken(token);
ctx.body = {
isValid,
};
});
//驗(yàn)證用戶(hù)名和密碼
function verifyUsernamePassword(user) {
//在此可以定義后端返回得數(shù)據(jù)格式
const index = users.findIndex((item) => {
return item.username === user.username && item.password == user.password;
});
const userInfo = users[index];
if (!userInfo) {
return undefined;
}
//2:user權(quán)限數(shù)字
const token = generatorToken(user.id, Auth.USER);
return token;
}
//驗(yàn)證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內(nèi)容需要使用Bodyparser
app
.use(bodyParser())
.use(tokenRouter.routes())
.listen(5000);
utils.js中g(shù)eneratorToken方法用來(lái)生成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,驗(yàn)證token信息以及進(jìn)行權(quán)限認(rèn)證相關(guān)邏輯
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,生成令牌和驗(yàn)證時(shí)使用
module.exports = {
secretKey: "jK123uu_s$!",
expiresIn: 24 * 60 * 60,
};
內(nèi)容相關(guān)api
const Auth = require("../../middlewares/auth");
const Router = require("@koa/router");
const router = new Router();
router.get("/content", (ctx) => {
ctx.body = {
result: "獲取內(nèi)容成功",
msg: "操作成功",
status: true,
};
});
router.post("/content", new Auth(5).middleware, (ctx) => {
ctx.body = {
result: "新增內(nèi)容成功",
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 {
//驗(yàn)證token
var decoded = jwt.verify(token.name, secretKey);
} catch (e) {
//token過(guò)期
if (e.name === "tokenExpiredError") {
errMsg = "token已過(guò)期";
}
ctx.body = {
errCode: 10005,
msg: errMsg,
request: `${ctx.body} ${ctx.path}`,
};
return;
}
//判斷用戶(hù)權(quán)限
if (decoded.scope < this.level) {
errMsg = "沒(méi)有權(quán)限";
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;