114.JWT(json web token)實(shí)現(xiàn)權(quán)限認(rèn)證過(guò)程

前方高能最新消息熱點(diǎn)公眾號(hào)首圖.jpg

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;

完整代碼:https://github.com/Cloverao/jwt

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容