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 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了(除了登录和注册,因为不存在),后端的中间件也能通过验证了

  • 24
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
1.项目代码均经过功能验证ok,确保稳定可靠运行。欢迎下载体验!下载完使用问题请私信沟通。 2.主要针对各个计算机相关专业,包括计算机科学、信息安全、数据科学与大数据技术、人工智能、通信、物联网等领域的在校学生、专业教师、企业员工。 3.项目具有丰富的拓展空间,不仅可作为入门进阶,也可直接作为毕设、课程设计、大作业、初期项目立项演示等用途。 4.当然也鼓励大家基于此进行二次开发。在使用过程中,如有问题或建议,请及沟通。 5.期待你能在项目中找到乐趣和灵感,也欢迎你的分享和反馈! 【资源说明】 基于Django后端+Vue前端+阿里云数据库实现完整的书店系统源码+详细部署说明.zip 项目简介: 实现一个提供网上购书功能的网站。 网站支持书商在上面开商店,购买者可以通过网站购买。 买家和卖家都可以注册自己的账号。 一个卖家可以开一个或多个网上商店 买家可以为自已的账户充值,在任意商店购买图书。 支持下单->付款->发货->收货 流程。 支持将喜欢的书本加入购物车 支持修改个人信息、修改密码、修改头像 支持修改店铺信息、删除店铺 支持添加书本、下架书本、上架书本、修改书本价格 支持买家查看所有订单,店铺查看所有店铺订单 支持买家取消订单,订单15分钟内未付款自动失效 支持全局搜索书本,搜索书店 支持店铺内搜索书本 支持首页推荐(最热店铺、最热书本、最近上新书本) 支持店内推荐(本店最热书本、最近上新书本) 项目采用Python Django框架编写后端所有代码(包含测试用例),总共实现38个接口,140+测试用例(均通过),项目代码总覆盖率90%+(其中接口覆盖率70%+),并包含Vue前端实现,前后端均纯原创!是一个完全可以上线使用的项目! 需求分析: (详情见需求分析.docx) API定义: (详情见API_doc文件夹) 前端设计: 1、 前端框架 ·框架:Vue ·脚手架:Vue-cli 2、 编程语言 ·javascript ·HTML ·css 3、 第三方插件 ·jquery ·bootstrap 4、 实现Vue组件(components) ·App.vue ·register.vue:用户注册 ·login.vue:用户登录 ·addstore.vue:新建店铺 ·balance.vue:查看余额/余额充值 ·book.vue:书本详情页 ·bookresult.vue:书本查询结果 ·buystore.vue:店铺详情页 ·information.vue:个人信息 ·mycart.vue:我的购物车 ·myorder.vue:我的订单 ·mystore.vue:我的店铺 ·order.vue:生成订单 ·orderinfo.vue:订单详情页 ·password.vue:修改密码 ·store.vue:店铺主页 ·storeorderinfo.vue:店铺订单详情页 ·storeorders.vue:店铺订单 ·storeresult.vue:店铺查询结果 ·TagsInput.vue:标签组件(book.vue的子组件) ·tmp.vue:中间跳转页 ·unregister.vue:注销用户 数据库设计: 项目需要有买家和卖家的区别,但是实际上他们可以由同一个表存储,所以开始设计数据库就有两种结构,一为(User to User 设计),另一为(Seller to Buyer 设计),为方便前端设计以及后端架构,采用User to User设计。
在 Spring Boot 后端,我们可以使用 Spring Security 框架来实现基于 token 的认证和授权。具体步骤如下: 1. 在用户登录成功后,生成一个 token 并返回给前端。 2. 前端在后续的请求中,在请求头中添加一个名为 Authorization 的字段,值为 "Bearer " + token。 3. 后端在接收到请求后,从请求头中解析出 token。 4. 后端使用 Spring Security 的 filter 对 token 进行验证,判断该请求是否有权限访问。 下面是 Spring Boot 后端的示例代码: 1. 在登录成功后,生成一个 JWT token 并返回给前端 ```java // 生成 JWT token String token = Jwts.builder() .setSubject(user.getUsername()) .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) .signWith(SignatureAlgorithm.HS512, SECRET) .compact(); // 将 token 返回给前端 response.addHeader(HEADER_STRING, TOKEN_PREFIX + token); ``` 2. 前端请求头中添加 Authorization 字段 ```javascript // 在请求头中添加 Authorization 字段 axios.defaults.headers.common['Authorization'] = 'Bearer ' + token; ``` 3. 后端请求头中解析出 token ```java // 从请求头中获取 token String header = request.getHeader(HEADER_STRING); if (header == null || !header.startsWith(TOKEN_PREFIX)) { chain.doFilter(request, response); return; } String token = header.replace(TOKEN_PREFIX, ""); ``` 4. 后端使用 Spring Security 的 filter 对 token 进行验证 ```java // 使用 Spring Security 的 filter 对 token 进行验证 Authentication authentication = new JwtAuthenticationToken(token); SecurityContextHolder.getContext().setAuthentication(authentication); chain.doFilter(request, response); ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

栀椩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值