0


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

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

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

总体流程如下:

  1. 后端使用中间件开启接口请求验证,除登录/注册外所有接口的请求都需要携带验证参数才能正确发起请求
  2. 前端登录时,存储验证消息,也就是token
  3. 请求拦截器中设置请求头,写入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 instanceofError? 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()constloginCB=async()=>{const{ account, password }= form.value
    const data ={ account, password }
    loginFormRef.value.validate(asyncvalid=>{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";exportconst useUserStore =defineStore("user",()=>{const userInfo =ref({});constgetUserInfo=async(data)=>{const res =awaitloginAPI(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
      };// 登录日志awaitloginLogAPI({account: res.results.account,name: res.results.name,email: res.results.email,});// console.log(userInfo.value)};// 修改头像constchangeAvatar=(url)=>{
      userInfo.avatar = url;};// 修改姓名constchangeName=(name)=>{
      userInfo.name = name;};//   解决刷新页面丢失store信息的问题constclearUserInfo=()=>{
      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);});exportdefault http;

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

config.headers.Authorization = token;

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


本文转载自: https://blog.csdn.net/u012848304/article/details/135533567
版权归原作者 栀椩 所有, 如有侵权,请联系我们删除。

“node后端+vue前端实现接口请求时携带authorization验证”的评论:

还没有评论