node后端+vue前端实现接口请求时携带authorization验证

栀椩 2024-06-26 12:03:02 阅读 73

node后端+vue前端实现接口请求时携带authorization验证

我们在写web项目时,后端写好接口,前端想要调用后端接口时,除了登录注册页面,所有的请求都需要携带authorization,这样是为了避免随意通过接口调取数据的现象发生。这是写web项目时最基础的点,但是也挺麻烦的,涉及前后端好几个地方的编码,经常忘记怎么写的,现在记录一下。

总体流程如下:

后端使用中间件开启接口请求验证,除登录/注册外所有接口的请求都需要携带验证参数才能正确发起请求前端登录时,存储验证消息,也就是token请求拦截器中设置请求头,写入authorization

大体就这么几个步骤,下面细化

一、后端开启接口请求验证

我是用node写的后端,请求验证写在后端入口程序app.js中,完整代码如下:

const express = require("express");

const cors = require("cors");

const bodyParser = require("body-parser");

const multer = require("multer");

const upload = multer({ dest: "./public/upload" });

const morgan = require("morgan");

const fs = require("fs");

const app = express();

// 创建一个写入流,将日志写入access.log文件

const accessLogStream = fs.createWriteStream("./access.log", { flags: "a" });

// 使用Morgan中间件,将日志写入控制台和文件

app.use(morgan("combined", { stream: accessLogStream }));

app.use(cors());

app.use(express.urlencoded({ extended: false }));

app.use(bodyParser.json());

// 托管静态文件

app.use(upload.any());

app.use(express.static("./public"));

// 处理错误的中间件

app.use((req, res, next) => {

res.cc = (err, status = 1) => {

res.send({

status,

message: err instanceof Error ? err.message : err,

});

};

next();

});

const jwtconfig = require("./jwt_config/index");

const { expressjwt: jwt } = require("express-jwt");

app.use(

jwt({

secret: jwtconfig.jwtSecretKey,

algorithms: ["HS256"],

}).unless({

path: [/^\/api\/user\/.*$/],

})

);

const userManagerRouter = require("./router/user");

app.use("/api/user", userManagerRouter);

const userInfoManageRouter = require("./router/userinfo");

app.use("/api/userinfo", userInfoManageRouter);

const settingRouter = require("./router/setting");

app.use("/api/setting", settingRouter);

const productRouter = require("./router/product");

app.use("/api/product", productRouter);

const messageRouter = require("./router/message");

app.use("/api/message", messageRouter);

const filesRouter = require("./router/files");

app.use("/api/files", filesRouter);

const logRouter = require("./router/log");

app.use("/api/log", logRouter);

const overviewRouter = require('./router/overview')

app.use('/api/overview', overviewRouter)

// 用户消息读取情况

const dmMsgRouter = require('./router/department_msg')

app.use('/api/dm', dmMsgRouter)

// 对不符合joi规则的情况进行报错

// app.use((err, req, res, next) => {

// if (err instanceof Joi.ValidationError) return res.cc(err.details[0].message);

// else res.cc(err);

// });

app.listen(3088, () => {

console.log("api server running at http://127.0.0.1:3088");

});

这个程序太长,相关的代码如下:

const jwtconfig = require("./jwt_config/index");

const { expressjwt: jwt } = require("express-jwt");

app.use(

jwt({

secret: jwtconfig.jwtSecretKey,

algorithms: ["HS256"],

}).unless({

path: [/^\/api\/user\/.*$/],

})

);

其实这个写法相对来说是固定的,首先,导入自己写好的jwt验证规则(也叫秘钥),其实就是一个jwtSecretKey,./jwt_config目录下的index.js文件如下:

module.exports = {

jwtSecretKey: 'xxx' // 改成自己的秘钥

}

然后导入express-jwt,接下来就是使用中间件来设定接口路由规则了,unless方法里面写的是排除的接口地址,是用正则表达式来排除的,/^\/api\/user\/.*$/这个正则表达式的意思是排除所有以/api/user/开头的接口

可以看上面的完整代码,app.use("/api/user", userManagerRouter);这里以/api/user/开头的接口都是给用户登录和注册相关的接口

后端按这个思路写就行了

二、登录存储token

这里有两种存储方式,一种是把token存储在localstorage中,另外一种是存储在全局数据管理工具中(也就是vuex或者pinia中),这里设计前后端联调

1、后端写登录接口,向前端传递token

先看看我的完整的登录接口处理函数

exports.login = (req, res) => {

// res.send("login");

const userInfo = req.body;

const sql = "select * from users where account = ?";

db.query(sql, userInfo.account, (err, results) => {

if (err) return res.cc(err);

if (results.length !== 1) return res.cc("用户不存在");

const compareResult = bcrypt.compareSync(

userInfo.password,

results[0].password

);

if (!compareResult) return res.cc("密码错误");

// 判断账号是否冻结

if (results[0].status == 1) return res.cc("账号被冻结");

const user = {

...results[0],

password: "",

imageUrl: "",

create_time: "",

update_time: "",

};

const tokenStr = jwt.sign(user, jwt_config.jwtSecretKey, {

expiresIn: "10h",

});

res.send({

status: 0,

results: results[0],

message: '登录成功',

token: "Bearer " + tokenStr

})

});

};

相关的代码如下:

const tokenStr = jwt.sign(user, jwt_config.jwtSecretKey, {

expiresIn: "10h",

});

res.send({

status: 0,

results: results[0],

message: '登录成功',

token: "Bearer " + tokenStr

})

这里其实很简单,就是后端通过秘钥生成一个有效期为10小时的token,这里的秘钥也是上面提到的,然后向前端发送这个token

2、前端登录时,存储token

我用的vue3,数据存储在pinia中,看看我的前端登录代码

import { useUserStore } from '@/stores/user'

const userStore = useUserStore()

const loginCB = async () => {

const { account, password } = form.value

const data = { account, password }

loginFormRef.value.validate(async valid => {

if (valid) {

try {

await userStore.getUserInfo(data)

// console.log(userStore.userInfo)

if (!userStore.userInfo.token) return ElMessage.error('用户名或密码错误')

// console.log(userStore.vue3ManageUserInfo)

router.push('/')

// console.log(results)

} catch (error) {

ElMessage.error('用户名或密码错误')

console.log(error)

}

} else {

ElMessage.error('没通过校验')

}

})

}

上面这段代码不是完整的,loginCB是登录按钮的回调函数,从回调中看到,其实我的登录是写在pinia的getUserInfo方法中的,继续看看这个store中的写法

import { ref } from "vue";

import { defineStore } from "pinia";

import { loginAPI } from "@/apis/user";

import { getUserInfoAPI } from "@/apis/userinfo";

import { loginLogAPI } from "@/apis/log";

export const useUserStore = defineStore(

"user",

() => {

const userInfo = ref({ });

const getUserInfo = async (data) => {

const res = await loginAPI(data);

// console.log(res);

userInfo.value = {

account: res.results.account,

token: res.token,

avatar: res.results.image_url,

id: res.results.id,

name: res.results.name,

sex: res.results.sex,

email: res.results.email,

department: res.results.department,

identity: res.results.identity

};

// 登录日志

await loginLogAPI({

account: res.results.account,

name: res.results.name,

email: res.results.email,

});

// console.log(userInfo.value)

};

// 修改头像

const changeAvatar = (url) => {

userInfo.avatar = url;

};

// 修改姓名

const changeName = (name) => {

userInfo.name = name;

};

// 解决刷新页面丢失store信息的问题

const clearUserInfo = () => {

userInfo.value = { };

};

return {

userInfo,

getUserInfo,

clearUserInfo,

changeAvatar,

changeName,

};

},

{

persist: true,

}

);

pinia中我用的是组合式api的写法,其实也是比较流行的写法,逻辑和语法都比较清楚

可以看到,我定义了一个userInfo的state,登录需要调用loginAPI,这个接口对应后端的login函数,请求到数据后,将接口返回的token写入到userInfo.token中,这样,全局数据管理store中就存储了一个包含token的名为userInfo的state(真的绕。。。)

注意,组合式api需要将state return出去

{

persist: true,

}

上面这个是持久化存储,我在“vue3+pinia用户信息持久缓存(token)的问题”这篇博客中有记录,目的是将userInfo存储到localstorage中

这样,登录时要做的工作就做完了

三、请求拦截器中携带token

使用axios发起接口请求,对axios进行二次封装,封装代码如下:

import axios from "axios";

import { ElMessage } from "element-plus";

import { useUserStore } from "@/stores/user";

// 创建axios实例

const http = axios.create({

baseURL: "http://127.0.0.1:xxxx/api/", // 改成自己的端口

timeout: 5000,

});

// axios请求拦截器

http.interceptors.request.use(

(config) => {

const userStore = useUserStore();

const token = userStore.userInfo.token;

if (token) {

config.headers.Authorization = token;

}

return config;

},

(e) => Promise.reject(e)

);

// axios响应式拦截器

http.interceptors.response.use(

(res) => res.data,

(e) => {

ElMessage.warning("接口响应出错");

console.log(e);

return Promise.reject(e);

}

);

export default http;

注意看请求拦截器中的代码,用到了刚刚上面提到的pinia中的userInfo这个state,首先获取userInfo中的token,判断是否存在token,存在的话,就把它写到请求头中去,关键的代码就是下面这行:

config.headers.Authorization = token;

这样,每次向后端发起数据请求的时候,都会携带这个token了(除了登录和注册,因为不存在),后端的中间件也能通过验证了



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。