nodejs+mysql+vue3 应用实例分析

打印 上一主题 下一主题

主题 1644|帖子 1644|积分 4932

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
node.js + vue3 +mysql 应用

以下是一个使用koa作为后端(包含登录、token验证、用户增编削查和权限控制),MySQL作为数据库,Vue 3作为前端展示的示例:
后端实现(koa部门)

1. 项目初始化与依赖安装

创建后端项目文件夹,在下令行进入该文件夹后执行以下下令:
  1. npm init -y
  2. npm install koa koa-router koa-bodyparser jsonwebtoken mysql2 bcryptjs cors
复制代码
2. 目次结构

  1. backend
  2. |-- app.js
  3. |-- controllers
  4. |   |-- authController.js
  5. |   |-- userController.js
  6. |-- middlewares
  7. |   |-- authMiddleware.js
  8. |-- routes
  9. |   |-- index.js
  10. |   |-- user.js
  11. |-- models
  12. |   |-- user.js
  13. |-- config
  14. |   |-- db.js
复制代码
3. config/db.js - 数据库配置与毗连

  1. const mysql = require('mysql2/promise');
  2. const pool = mysql.createPool({
  3.     host: 'localhost',
  4.     user: 'root',
  5.     password: 'your_password',
  6.     database: 'your_database_name',
  7.     waitForConnections: true,
  8.     connectionLimit: 10,
  9.     queueLimit: 0
  10. });
  11. module.exports = pool;
复制代码
4. models/user.js - 用户模型及数据库操纵

  1. const pool = require('../config/db.js');
  2. class User {
  3.     static async findByUsername(username) {
  4.         const [rows] = await pool.query('SELECT * FROM users
  5.         WHERE username =?', [username]);
  6.         return rows[0];
  7.     }
  8.     static async findById(id) {
  9.         const [rows] = await pool.query('SELECT * FROM users WHERE id =?', [id]);
  10.         return rows[0];
  11.     }
  12.     static async create(userData) {
  13.         const { username, password, role, menus } = userData;
  14.         const [result] = await pool.query('INSERT INTO users
  15.         (username, password, role, menus) VALUES (?,?,?,?)',
  16.         [username, password, role, menus]);
  17.         return result.insertId;
  18.     }
  19.     static async update(id, updatedUser) {
  20.         const { password, role, menus } = updatedUser;
  21.         const query = 'UPDATE users SET password =?, role =?,
  22.          menus =? WHERE id =?';
  23.         const [result] = await pool.query(query, [password, role, menus, id]);
  24.         return result.affectedRows > 0;
  25.     }
  26.     static async delete(id) {
  27.         const [result] = await pool.query('DELETE FROM users WHERE id =?', [id]);
  28.         return result.affectedRows > 0;
  29.     }
  30.     static async getAll() {
  31.         const [rows] = await pool.query('SELECT * FROM users');
  32.         return rows;
  33.     }
  34. }
  35. module.exports = User;
复制代码
5. controllers/authController.js - 认证控制器

  1. const bcrypt = require('bcryptjs');
  2. const jwt = require('jsonwebtoken');
  3. const User = require('../models/user.js');
  4. const login = async (ctx) => {
  5.     const { username, password } = ctx.request.body;
  6.     const user = await User.findByUsername(username);
  7.     if (user) {
  8.         const isMatch = await bcrypt.compare(password, user.password);
  9.         if (isMatch) {
  10.             const token = jwt.sign({ id: user.id, username: user.username,
  11.             role: user.role }, 'your_secret_key', { expiresIn: '1h' });
  12.             ctx.body = { success: true, token };
  13.         } else {
  14.             ctx.status = 401;
  15.             ctx.body = { success: false, message: '密码错误' };
  16.         }
  17.     } else {
  18.         ctx.status = 404;
  19.         ctx.body = { success: false, message: '用户不存在' };
  20.     }
  21. };
  22. module.exports = {
  23.     login
  24. };
复制代码
6. controllers/userController.js - 用户操纵控制器

  1. const User = require('../models/user.js');
  2. const getUsers = async (ctx) => {
  3.     const users = await User.getAll();
  4.     ctx.body = users;
  5. };
  6. const getUserById = async (ctx) => {
  7.     const id = ctx.params.id;
  8.     const user = await User.findById(id);
  9.     if (user) {
  10.         ctx.body = user;
  11.     } else {
  12.         ctx.status = 404;
  13.         ctx.body = { message: '用户不存在' };
  14.     }
  15. };
  16. const updateUser = async (ctx) => {
  17.     const id = ctx.params.id;
  18.     const updatedUser = ctx.request.body;
  19.     const result = await User.update(id, updatedUser);
  20.     if (result) {
  21.         ctx.body = { message: '用户更新成功' };
  22.     } else {
  23.         ctx.status = 404;
  24.         ctx.body = { message: '用户不存在,无法更新' };
  25.     }
  26. };
  27. const deleteUser = async (ctx) => {
  28.     const id = ctx.params.id;
  29.     const result = await User.delete(id);
  30.     if (result) {
  31.         ctx.body = { message: '用户删除成功' };
  32.     } else {
  33.         ctx.status = 404;
  34.         ctx.body = { message: '用户不存在,无法删除' };
  35.     }
  36. };
  37. module.exports = {
  38.     getUsers,
  39.     getUserById,
  40.     updateUser,
  41.     deleteUser
  42. };
复制代码
7. middlewares/authMiddleware.js - token验证中间件

  1. const jwt = require('jsonwebtoken');
  2. const authMiddleware = async (ctx, next) => {
  3.     const token = ctx.headers.authorization && ctx.headers.authorization
  4.     .split(' ')[1];
  5.     if (token) {
  6.         try {
  7.             const decoded = jwt.verify(token, 'your_secret_key');
  8.             ctx.state.user = decoded;
  9.             await next();
  10.         } catch (error) {
  11.             ctx.status = 401;
  12.             ctx.body = { success: false, message: '无效的token' };
  13.         }
  14.     } else {
  15.         ctx.status = 401;
  16.         ctx.body = { success: false, message: '未提供token' };
  17.     }
  18. };
  19. module.exports = authMiddleware;
复制代码
8. routes/index.js - 首页路由

  1. const Router = require('koa-router');
  2. const router = new Router();
  3. router.get('/', (ctx) => {
  4.     ctx.body = '这是首页';
  5. });
  6. module.exports = router;
复制代码
9. routes/user.js - 用户管理路由

  1. const Router = require('koa-router');
  2. const userController = require('../controllers/userController.js');
  3. const authMiddleware = require('../middlewares/authMiddleware.js');
  4. const router = new Router({ prefix: '/users' });
  5. router.get('/', authMiddleware, userController.getUsers);
  6. router.get('/:id', authMiddleware, userController.getUserById);
  7. router.put('/:id', authMiddleware, userController.updateUser);
  8. router.delete('/:id', authMiddleware, userController.deleteUser);
  9. module.exports = router;
复制代码
10. app.js - 应用入口文件

  1. const Koa = require('koa');
  2. const bodyParser = require('koa-bodyparser');
  3. const cors = require('cors');
  4. const indexRouter = require('./routes/index.js');
  5. const userRouter = require('./routes/user.js');
  6. const authController = require('./controllers/authController.js');
  7. const Router = require('koa-router');
  8. const app = new Koa();
  9. const router = new Router();
  10. // 使用cors中间件,允许跨域请求
  11. app.use(cors());
  12. // 应用中间件
  13. app.use(bodyParser());
  14. // 登录路由
  15. router.post('/login', authController.login);
  16. // 挂载首页路由
  17. app.use(indexRouter.routes()).use(indexRouter.allowedMethods());
  18. // 挂载用户管理路由
  19. app.use(userRouter.routes()).use(userRouter.allowedMethods());
  20. // 挂载总路由
  21. app.use(router.routes()).use(router.allowedMethods());
  22. app.listen(3000, () => {
  23.     console.log('后端服务器启动,监听3000端口');
  24. });
复制代码
前端实现(Vue 3部门)

1. 创建Vue 3项目

使用@vue/cli创建项目(如果没有安装,先执行npm install -g @vue/cli):
  1. vue create frontend
复制代码
在创建项目过程中选择Vue 3相干选项。
2. 目次结构(主要部门)

  1. frontend
  2. |-- src
  3. |   |-- components
  4. |   |   |-- Login.vue
  5. |   |   |-- Home.vue
  6. |   |   |-- Detail.vue
  7. |   |   |-- UserManagement.vue
  8. |   |-- router
  9. |   |   |-- index.js
  10. |   |-- store
  11. |   |   |-- index.js
  12. |   |-- main.js
复制代码
3. src/main.js - 入口文件

  1. import { createApp } from 'vue';
  2. import App from './App.vue';
  3. import router from './router/index';
  4. import store from './store/index';
  5. createApp(App).use(router).use(store).mount('#app');
复制代码
4. src/router/index.js - 路由配置

  1. import { createRouter, createWebHistory } from 'vue-router';
  2. import Login from '../components/Login.vue';
  3. import Home from '../components/Home.vue';
  4. import Detail from '../components/Detail.vue';
  5. import UserManagement from '../components/UserManagement.vue';
  6. const routes = [
  7.     { path: '/', redirect: '/login' },
  8.     { path: '/login', component: Login },
  9.     { path: '/home', component: Home },
  10.     { path: '/detail', component: Detail },
  11.     { path: '/user-management', component: UserManagement }
  12. ];
  13. const router = createRouter({
  14.     history: createWebHistory(),
  15.     routes
  16. });
  17. export default router;
复制代码
5. src/components/Login.vue - 登录组件

  1. <template>
  2.     <div>
  3.         <input v-model="username" placeholder="用户名" />
  4.         <input type="password" v-model="password" placeholder="密码" />
  5.         <button @click="login">登录</button>
  6.     </div>
  7. </template>
  8. <script>
  9. import axios from 'axios';
  10. export default {
  11.     data() {
  12.         return {
  13.             username: '',
  14.             password: ''
  15.         };
  16.     },
  17.     methods: {
  18.         async login() {
  19.             try {
  20.                 const response = await axios.post('http://localhost:3000/login',
  21.                 {
  22.                     username: this.username,
  23.                     password: this.password
  24.                 });
  25.                 if (response.data.success) {
  26.                     localStorage.setItem('token', response.data.token);
  27.                     this.$router.push('/home');
  28.                 }
  29.             } catch (error) {
  30.                 console.error('登录失败', error);
  31.             }
  32.         }
  33.     }
  34. };
  35. </script>
复制代码
6. src/components/Home.vue - 首页组件

  1. <template>
  2.     <div>这是首页</div>
  3. </template>
  4. <script>
  5. export default {
  6.     // 这里可以添加需要的逻辑,比如获取用户信息(如果有需要)
  7. };
  8. </script>
复制代码
7. src/components/Detail.vue - 详情页组件

  1. <template>
  2.     <div>这是详情页</div>
  3. </template>
  4. <script>
  5. export default {
  6.     // 这里可以添加详情页相关逻辑
  7. };
  8. </script>
复制代码
8. src/components/UserManagement.vue - 用户管理组件

  1. <template>
  2.     <div>
  3.         <button v-if="user.role === 'admin'" @click="getUsers">获取用户列表</button>
  4.         <!-- 这里可以添加更多用户管理相关的UI和操作 -->
  5.     </div>
  6. </template>
  7. <script>
  8. import axios from 'axios';
  9. export default {
  10.     data() {
  11.         return {
  12.             user: {},
  13.             users: []
  14.         };
  15.     },
  16.     created() {
  17.         const token = localStorage.getItem('token');
  18.         if (token) {
  19.             axios.get('http://localhost:3000/users', {
  20.                 headers: {
  21.                     Authorization: 'Bearer'+ token
  22.                 }
  23.             }).then(response => {
  24.                 this.user = response.data;
  25.             }).catch(error => {
  26.                 console.error('获取用户信息失败', error);
  27.             });
  28.         }
  29.     },
  30.     methods: {
  31.         async getUsers() {
  32.             const token = localStorage.getItem('token');
  33.             try {
  34.                 const response = axios.get('http://localhost:3000/users', {
  35.                     headers: {
  36.                         Authorization: 'Bearer'+ token
  37.                     }
  38.                 });
  39.                 this.users = (await response).data;
  40.             } catch (error) {
  41.                 console.error('获取用户列表失败', error);
  42.             }
  43.         }
  44.     }
  45. };
  46. </script>
复制代码
9. src/store/index.js - Vuex存储(示例,可根据需要扩展)

  1. import { createStore } from 'vuex';
  2. const store = createStore({
  3.     state() {
  4.         return {
  5.             user: null
  6.         };
  7.     },
  8.     mutations: {
  9.         setUser(state, user) {
  10.             state.user = user;
  11.         }
  12.     },
  13.     actions: {
  14.         // 可以添加获取用户信息等相关的异步操作
  15.     }
  16. });
  17. export default store;
复制代码
注意事项


  • 在实际应用中,需要对密码举行更安全的处置处罚,好比使用更强的bcrypt哈希算法参数。
  • 对于token的存储和验证机制,可以进一步优化,好比添加token刷新逻辑。
  • 前端的axios请求可以添加更多的错误处置处罚和加载状态提示。
  • 权限控制在前端可以根据后端返回的用户脚色和菜单权限更精细地控制组件的表现和隐藏。
小结

以下是上述代码实例直策应用时需要注意的一些总结与建议:
一、安全相干


  • 密码加密强度

    • 注意事项:在后端的 authController.js 中使用 bcryptjs 举行密码加密时,应公道设置加密资本因子(cost 参数)来增强密码安全性。默认的 cost 值可能不足以抵抗强力的暴力破解攻击。
    • 实例分析:例如,当前代码可能像如许使用 bcrypt 比较密码(简化示例):
    1. const isMatch = await bcrypt.compare(password, user.password);
    复制代码
    但没有设置 cost 参数,实际应用中可在密码创建或更新时明确设置符合的强度,像如许:
    1. const saltRounds = 12; // 合适的成本因子,可根据服务器性能等调整
    2. const hashedPassword = await bcrypt.hash(newPassword, saltRounds);
    复制代码

  • Token 管理

    • 注意事项:token 的天生与验证使用固定的 secret_key,且缺少刷新机制,同时存储和传输过程也需更安全的处置处罚。
    • 实例分析

      • 在 authController.js 中天生 token 时:
      1. const token = jwt.sign({ id: user.id, username: user.username,
      2. role: user.role }, 'your_secret_key', { expiresIn: '1h' });
      复制代码
      这里的 your_secret_key 应配置为情况变量,避免硬编码在代码中导致泄露风险,好比在服务器启动时通过情况变量读取:
      1. const secretKey = process.env.JWT_SECRET_KEY;
      2. const token = jwt.sign({... }, secretKey, { expiresIn: '1h' });
      复制代码
         

      • 关于 token 刷新,当 token 快逾期时,前端没有机制向背景请求新的 token 来无缝续期,用户体验不佳,需要实现雷同的逻辑,例如设置定时器,在 token 有效期的一定时间(如还剩 10 分钟逾期时)向专门的 token 刷新接口发起请求获取新 token。


  • 数据库毗连安全

    • 注意事项:数据库毗连配置中用户名、密码等信息硬编码不安全,并且缺少美满的错误处置处罚和毗连池管理。
    • 实例分析:在 config/db.js 中:
      1. const pool = mysql.createPool({
      2.     host: 'localhost',
      3.     user: 'root',
      4.     password: 'your_password',
      5.     database: 'your_database_name',
      6.     //... 其他配置
      7. });
      复制代码
      应改为通过情况变量来设置这些敏感信息,如:
      1. const pool = mysql.createPool({
      2.     host: process.env.DB_HOST,
      3.     user: process.env.DB_USER,
      4.     password: process.env.DB_PASSWORD,
      5.     database: process.env.DB_NAME,
      6.     //... 其他配置
      7. });
      复制代码
      同时,对于数据库操纵可能出现的错误(如毗连失败、查询超时等),现在只是简单返回结果,没有针对性的错误处置处罚,例如在 models/user.js 中查询用户时,如果数据库毗连出现问题,应举行更好的错误捕捉和处置处罚,像如许:
      1. static async findByUsername(username) {
      2.     try {
      3.         const [rows] = await pool.query('SELECT * FROM users
      4.         WHERE username =?', [username]);
      5.         return rows[0];
      6.     } catch (error) {
      7.         console.error('数据库查询用户出错:', error);
      8.         throw new Error('数据库查询用户时遇到问题,请稍后再试');
      9.     }
      10. }
      复制代码

二、错误处置处罚


  • 同一错误处置处罚机制

    • 注意事项:后端各个功能模块中的错误处置处罚较分散、简单,缺乏同一规范,不利于排查和定位问题。
    • 实例分析:好比在 userController.js 中差异操纵(如获取用户、更新用户等)都各自返回简单的错误信息,像更新用户操纵:
      1. const updateUser = async (ctx) => {
      2.     const id = ctx.params.id;
      3.     const updatedUser = ctx.request.body;
      4.     const result = await User.update(id, updatedUser);
      5.     if (result) {
      6.         ctx.body = { message: '用户信息更新成功' };
      7.     } else {
      8.         ctx.status = 404;
      9.         ctx.body = { message: '用户不存在,无法更新' };
      10.     }
      11. };
      复制代码
      可以创建一个同一的错误处置处罚中间件,将各种错误举行分类,返回更规范、具体的错误响应给前端,例如创建 errorMiddleware.js:
      1. module.exports = async (ctx, next) => {
      2.     try {
      3.         await next();
      4.     } catch (error) {
      5.         ctx.status = error.statusCode || 500;
      6.         ctx.body = {
      7.             success: false,
      8.             message: error.message || '服务器内部错误'
      9.         };
      10.     }
      11. };
      复制代码
      然后在 app.js 中应用这个中间件,使整个应用能同一处置处罚错误情况。

三、代码结构与可维护性


  • 分层与模块化

    • 注意事项:随着业务扩展,现在代码的分层不够清晰,业务逻辑分散在控制器和模型中,不利于维护和扩展,可添加服务层来封装业务逻辑。
    • 实例分析:例如用户注册功能,如果后续需要添加更多复杂逻辑(如验证手机号格式、发送注册短信验证码等),现在这些逻辑可能会散落在控制器里的注册方法中,更好的做法是创建 userService.js 服务层文件,在里面封装注册相干的完备逻辑,像如许:
    1. const User = require('./models/user.js');
    2. const bcrypt = require('bcryptjs');
    3. const register = async (userData) => {
    4.     // 验证手机号等额外逻辑(此处省略具体实现)
    5.     const hashedPassword = await bcrypt.hash(userData.password, 10);
    6.     const newUser = {
    7.         username: userData.username,
    8.         password: hashedPassword,
    9.         role: userData.role,
    10.         menus: userData.menus
    11.     };
    12.     return User.create(newUser);
    13. };
    14. module.exports = {
    15.     register
    16. };
    复制代码
    然后在 authController.js 中调用这个服务层方法来举行注册操纵,使得业务逻辑更加清晰,便于后续维护和扩展。
  • 数据库操纵可移植性

    • 注意事项:直接写 SQL 语句举行数据库操纵,后期若更换数据库(如从 MySQL 换为 PostgreSQL),改动资本大,可考虑引入 ORM 框架。
    • 实例分析:当前在 models/user.js 中有很多原生 SQL 查询语句,如:
    1. static async findByUsername(username) {
    2.     const [rows] = await pool.query
    3.     ('SELECT * FROM users WHERE username =?', [username]);
    4.     return rows[0];
    5. }
    复制代码
    若使用 Sequelize(一种常见 ORM 框架),代码可以改为:
    1. const { Model, DataTypes } = require('sequelize');
    2. const sequelize = require('../config/database');
    3. class User extends Model {}
    4. User.init({
    5.     id: {
    6.         type: DataTypes.INTEGER,
    7.         autoIncrement: true,
    8.         primaryKey: true
    9.     },
    10.     username: {
    11.         type: DataTypes.STRING,
    12.         allowNull: false,
    13.         unique: true
    14.     },
    15.     password: {
    16.         type: DataTypes.STRING,
    17.         allowNull: false
    18.     },
    19.     role: {
    20.         type: DataTypes.STRING,
    21.         allowNull: false
    22.     },
    23.     menus: {
    24.         type: DataTypes.TEXT
    25.     },
    26.     created_at: {
    27.         type: DataTypes.DATE,
    28.         defaultValue: DataTypes.NOW
    29.     },
    30.     updated_at: {
    31.         type: DataTypes.DATE,
    32.         defaultValue: DataTypes.NOW,
    33.         onUpdate: DataTypes.NOW
    34.     }
    35. }, {
    36.     sequelize,
    37.     modelName: 'User'
    38. });
    39. module.exports = User;
    复制代码
    如许后续切换数据库时,只需调整 Sequelize 的配置,而不消大量修改具体的数据库操纵语句。
四、权限控制


  • 细粒度权限

    • 注意事项:当前基于用户脚色和菜单的权限控制较粗,实际可能需要更细粒度,好比对具体功能按钮等操纵举行权限管控。
    • 实例分析:在前端 UserManagement.vue 组件中,现在只是简单根据脚色判定是否表现获取用户列表按钮:
    1. <button v-if="user.role === 'admin'" @click="getUsers">获取用户列表</button>
    复制代码
    但可能对于“删除用户”这个操纵,不仅要判定脚色是 admin,还需要后端进一步返回针对每个用户是否有删除权限的具体标识,前端根据这个标识来决定按钮是否可用,后端则在对应的删除用户接口处具体校验这个权限,好比在 userController.js 的 deleteUser 方法中添加更细的权限判定逻辑:
    1. const deleteUser = async (ctx) => {
    2.     const id = ctx.params.id;
    3.     const user = ctx.state.user;
    4.     if (user.role === 'admin' && user.permissions.includes('delete_user')) {
    5.         const result = await User.delete(id);
    6.         if (result) {
    7.             ctx.body = { message: '用户删除成功' };
    8.         } else {
    9.             ctx.status = 404;
    10.             ctx.body = { message: '用户不存在,无法删除' };
    11.         }
    12.     } else {
    13.         ctx.status = 403;
    14.         ctx.body = { message: '您没有权限执行此操作' };
    15.     }
    16. };
    复制代码

  • 权限数据校验

    • 注意事项:后端没有对前端通报的权限相干数据严格校验,存在前端篡改数据越权访问风险。
    • 实例分析:当前前端获取用户信息后可以拿到权限相干数据(如脚色、菜单权限等),若前端恶意篡改这些数据再发起请求,后端没有有效校验机制就可能导致越权访问。好比在后端的 authMiddleware.js 中验证 token 后获取用户信息时,可以增长对权限数据格式、范围等的校验逻辑,确保其合法性,像如许:
    1. const authMiddleware = async (ctx, next) => {
    2.     const token = ctx.headers.authorization && ctx.headers.authorization
    3.     .split(' ')[1];
    4.     if (token) {
    5.         try {
    6.             const decoded = jwt.verify(token,'secret_key');
    7.             // 校验权限相关字段是否符合预期格式和范围
    8.             if (typeof decoded.role ==='string' &&
    9.             Array.isArray(decoded.menus)) {
    10.                 ctx.state.user = decoded;
    11.                 await next();
    12.             } else {
    13.                 ctx.status = 401;
    14.                 ctx.body = { success: false, message: '权限数据格式错误' };
    15.             }
    16.         } catch (error) {
    17.             ctx.status = 401;
    18.             ctx.body = { success: false, message: '无效的token' };
    19.         }
    20.     } else {
    21.         ctx.status = 401;
    22.         ctx.body = { success: false, message: '未提供token' };
    23.     }
    24. };
    复制代码

五、前端相干


  • Token 存储安全

    • 注意事项:前端将 token 存储在 localStorage 轻易遭受 XSS 攻击,应接纳更安全的存储方式。
    • 实例分析:在 Login.vue 组件中登录成功后如许存储 token:
    1. localStorage.setItem('token', response.data.token);
    复制代码
    可以考虑使用 cookie 并设置 httpOnly 属性来存储 token,在后端设置 cookie 相干属性(例如通过 koa 的中间件来设置),让前端无法通过脚本访问到 token,增强安全性。

  • 用户输入验证

    • 注意事项:前端对用户输入验证简单,轻易被使用发起恶意攻击,应加强输入框的验证。
    • 实例分析:在 Login.vue 组件中,用户名和密码输入框没有限制输入内容,像如许:
    1. <input v-model="username" placeholder="用户名" />
    2. <input type="password" v-model="password" placeholder="密码" />
    复制代码
    可以添加 v-validate(使用 vee-validate 等验证库)等方式来限制输入长度、格式等,例如:
    1. <input v-model="username" placeholder="用户名"
    2. v-validate="'required|min:3|max:20'" />
    3. <input type="password" v-model="password"
    4. placeholder="密码" v-validate="'required|min:6|max:20'" />
    复制代码
    并根据验证结果给出相应提示,防止不公道的输入通报到后端。

  • 接口请求优化与同一处置处罚

    • 注意事项:前端使用 axios 举行接口请求时,缺少同一的拦截处置处罚,导致代码冗余且不利于维护,同时存在重复请求等性能问题。
    • 实例分析:在多个组件(如 UserManagement.vue)中都有发起获取用户相干信息的请求,每次都要写雷同如许的代码:
    1. const token = localStorage.getItem('token');
    2. try {
    3.     const response = axios.get('http://localhost:3000/users', {
    4.         headers: {
    5.             Authorization: 'Bearer'+ token
    6.         }
    7.     });
    8.     this.users = (await response).data;
    9. } catch (error) {
    10.     console.error('获取用户列表失败', error);
    11. }
    复制代码
    可以创建 axios 实例并设置同一的请求拦截器(添加请求头、处置处罚加载状态等)和响应拦截器(处置处罚错误、同一解析数据等),例如在 src/api/index.js 创建:
    1. import axios from 'axios';
    2. const instance = axios.create({
    3.     baseURL: 'http://localhost:3000'
    4. });
    5. instance.interceptors.request.use((config) => {
    6.     const token = localStorage.getItem('token');
    7.     if (token) {
    8.         config.headers.Authorization = `Bearer ${token}`;
    9.     }
    10.     return config;
    11. }, (error) => {
    12.     return Promise.reject(error);
    13. });
    14. instance.interceptors.response.use((response) => {
    15.     return response.data;
    16. }, (error) => {
    17.     console.error('接口请求出错:', error);
    18.     return Promise.reject(error);
    19. });
    20. export default instance;
    复制代码
    然后在组件中直接使用这个 axios 实例举行请求,减少重复代码,也方便对请求举行同一管理和优化。

总之,上述代码示例要直策应用到实际生产情况,需要从安全、错误处置处罚、代码结构、权限控制以及前端相干等多方面举行美满和优化,以保障应用的稳固、安全和可维护性。
补充(前端将 token 存储在 localStorage 轻易遭受 XSS 攻击,应接纳更安全的存储方式,具体如下:)


  • HttpOnly Cookie

    • 原理:HttpOnly 是一个设置在 Cookie 上的属性,当一个 Cookie 被标记为 HttpOnly 时,通过浏览器端的脚本(如JavaScript)将无法访问该 Cookie。如许可以有效防止 XSS 攻击获取存储在 Cookie 中的敏感信息,如 token。
    • 后端设置示例(以koa为例)

  1. const Koa = require('koa');
  2. const app = new Koa();
  3. const cookie = require('koa-cookie');
  4. // 应用中间件
  5. app.use(cookie());
  6. // 假设在登录成功后设置token到Cookie
  7. const login = async (ctx) => {
  8.     //... 登录验证逻辑
  9.     const token = jwt.sign({... },'secret_key', { expiresIn: '1h' });
  10.     ctx.cookies.set('token', token, {
  11.         httpOnly: true,
  12.         // 还可以设置其他属性,如maxAge(过期时间,单位为毫秒)、path(Cookie生效的路径)等
  13.         maxAge: 3600 * 1000,
  14.         path: '/'
  15.     });
  16.     ctx.body = { success: true };
  17. };
复制代码


  • 前端注意事项:由于 HttpOnly 的特性,前端无法通过 document.cookie 等方式获取 token,因此在发送需要认证的请求时,浏览器会主动将带有 HttpOnly 属性的 Cookie 包含在请求头中(前提是请求的域名、路径等符合 Cookie 设置的要求)。但要注意,这种方式在跨域场景下可能需要额外配置(如设置 withCredentials 属性为 true),同时要确保后端正确处置处罚跨域的 Cookie。

  • Encrypted LocalStorage

    • 原理:在将 token 存储到 localStorage 之前,使用加密算法对其举行加密。如许即使攻击者可以或许访问 localStorage,获取到的也是加密后的内容,无法直接使用。在需要使用 token 时,再通过解密算法还原。
    • 示例代码(使用crypto-js库举行加密息争密)
    • 起首安装 crypto - js:

  1. npm install crypto-js
复制代码


  • 存储 token(在登录成功后,假设在一个 Vue 组件中):
  1. import CryptoJS from 'crypto-js';
  2. const SECRET_KEY = 'your_secret_key'; // 用于加密和解密的密钥,要妥善保管
  3. const storeToken = (token) => {
  4.     const encryptedToken = CryptoJS.AES.encrypt(token, SECRET_KEY).toString();
  5.     localStorage.setItem('encrypted_token', encryptedToken);
  6. };
复制代码


  • 获取和使用 token(在需要发送认证请求的组件方法中):
  1. const getToken = () => {
  2.     const encryptedToken = localStorage.getItem('encrypted_token');
  3.     if (encryptedToken) {
  4.         const bytes = CryptoJS.AES.decrypt(encryptedToken, SECRET_KEY);
  5.         const originalToken = bytes.toString(CryptoJS.enc.Utf8);
  6.         return originalToken;
  7.     }
  8.     return null;
  9. };
复制代码


  • 注意事项:加密密钥的安全性至关重要,不能在代码中硬编码,应该像存储 token 的 secret_key 一样,通过情况变量等方式配置。同时,加密息争密操纵可能会对性能产生一定的影响,尤其是在频繁存储和获取 token 的场景下。

  • Memory Storage(In - Memory Session)

    • 原理:将 token 存储在内存中,例如在 Vue 应用中,可以使用一个全局的变量(如 Vuex 状态管理中的状态)或者一个单例对象来存储 token。这种方式下,token 不会长期化存储在本地存储介质中,只要页面关闭或者应用退出,token 就会消散,因此可以避免一些存储层面的安全风险。
    • 示例(使用Vuex存储)
    • 在 src/store/index.js 中:

  1. import { createStore } from 'vuex';
  2. const store = createStore({
  3.     state() {
  4.         return {
  5.             token: null
  6.         };
  7.     },
  8.     mutations: {
  9.         setToken(state, token) {
  10.             state.token = token;
  11.         }
  12.     },
  13.     actions: {
  14.         // 例如在登录成功后设置token
  15.         loginSuccess({ commit }, token) {
  16.             commit('setToken', token);
  17.         }
  18.     }
  19. });
  20. export default store;
复制代码


  • 在登录组件(如 Login.vue)中:
  1. import { mapActions } from 'vuex';
  2. export default {
  3.     methods: {
  4.       ...mapActions(['loginSuccess']),
  5.         async login() {
  6.             try {
  7.                 const response = await axios.post('http://localhost:3000/login', {
  8.                     //... 登录参数
  9.                 });
  10.                 if (response.data.success) {
  11.                     this.loginSuccess(response.data.token);
  12.                     //... 其他逻辑,如路由跳转
  13.                 }
  14.             } catch (error) {
  15.                 console.error('登录失败', error);
  16.             }
  17.         }
  18.     }
  19. };
复制代码


  • 注意事项:这种方式的缺点是,如果用户刷新页面或者意外关闭标签页后重新打开,用户可能需要重新登录获取 token,会影响用户体验。为了缓解这个问题,可以结合其他存储方式(如前面提到的加密 localStorage)来在一定水平上恢复用户状态,但这也增长了复杂性和潜伏的安全风险。同时,在多标签页或者多窗口场景下,内存中的存储方式需要考虑如何在差异标签页之间同步 token 状态等问题。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

卖不甜枣

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表