当前位置: 首页 > article >正文

nodejs+mysql+vue3 应用实例剖析

文章目录

    • node.js + vue3 +mysql 应用
      • 后端实现(koa部分)
        • 1. 项目初始化与依赖安装
        • 2. 目录结构
        • 3. `config/db.js` - 数据库配置与连接
        • 4. `models/user.js` - 用户模型及数据库操作
        • 5. `controllers/authController.js` - 认证控制器
        • 6. `controllers/userController.js` - 用户操作控制器
        • 7. `middlewares/authMiddleware.js` - `token`验证中间件
        • 8. `routes/index.js` - 首页路由
        • 9. `routes/user.js` - 用户管理路由
        • 10. `app.js` - 应用入口文件
      • 前端实现(`Vue 3`部分)
        • 1. 创建`Vue 3`项目
        • 2. 目录结构(主要部分)
        • 3. `src/main.js` - 入口文件
        • 4. `src/router/index.js` - 路由配置
        • 5. `src/components/Login.vue` - 登录组件
        • 6. `src/components/Home.vue` - 首页组件
        • 7. `src/components/Detail.vue` - 详情页组件
        • 8. `src/components/UserManagement.vue` - 用户管理组件
        • 9. `src/store/index.js` - `Vuex`存储(示例,可根据需要扩展)
      • 注意事项
    • 小结
      • 一、安全相关
      • 二、错误处理
      • 三、代码结构与可维护性
      • 四、权限控制
      • 五、前端相关
    • 补充(前端将 token 存储在 localStorage 容易遭受 XSS 攻击,应采用更安全的存储方式,具体如下:)

node.js + vue3 +mysql 应用

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

后端实现(koa部分)

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

创建后端项目文件夹,在命令行进入该文件夹后执行以下命令:

npm init -y
npm install koa koa-router koa-bodyparser jsonwebtoken mysql2 bcryptjs cors
2. 目录结构
backend
|-- app.js
|-- controllers
|   |-- authController.js
|   |-- userController.js
|-- middlewares
|   |-- authMiddleware.js
|-- routes
|   |-- index.js
|   |-- user.js
|-- models
|   |-- user.js
|-- config
|   |-- db.js
3. config/db.js - 数据库配置与连接
const mysql = require('mysql2/promise');

const pool = mysql.createPool({
    host: 'localhost',
    user: 'root',
    password: 'your_password',
    database: 'your_database_name',
    waitForConnections: true,
    connectionLimit: 10,
    queueLimit: 0
});

module.exports = pool;
4. models/user.js - 用户模型及数据库操作
const pool = require('../config/db.js');

class User {
    static async findByUsername(username) {
        const [rows] = await pool.query('SELECT * FROM users 
        WHERE username =?', [username]);
        return rows[0];
    }

    static async findById(id) {
        const [rows] = await pool.query('SELECT * FROM users WHERE id =?', [id]);
        return rows[0];
    }

    static async create(userData) {
        const { username, password, role, menus } = userData;
        const [result] = await pool.query('INSERT INTO users 
        (username, password, role, menus) VALUES (?,?,?,?)', 
        [username, password, role, menus]);
        return result.insertId;
    }

    static async update(id, updatedUser) {
        const { password, role, menus } = updatedUser;
        const query = 'UPDATE users SET password =?, role =?,
         menus =? WHERE id =?';
        const [result] = await pool.query(query, [password, role, menus, id]);
        return result.affectedRows > 0;
    }

    static async delete(id) {
        const [result] = await pool.query('DELETE FROM users WHERE id =?', [id]);
        return result.affectedRows > 0;
    }

    static async getAll() {
        const [rows] = await pool.query('SELECT * FROM users');
        return rows;
    }
}

module.exports = User;
5. controllers/authController.js - 认证控制器
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const User = require('../models/user.js');

const login = async (ctx) => {
    const { username, password } = ctx.request.body;
    const user = await User.findByUsername(username);
    if (user) {
        const isMatch = await bcrypt.compare(password, user.password);
        if (isMatch) {
            const token = jwt.sign({ id: user.id, username: user.username, 
            role: user.role }, 'your_secret_key', { expiresIn: '1h' });
            ctx.body = { success: true, token };
        } else {
            ctx.status = 401;
            ctx.body = { success: false, message: '密码错误' };
        }
    } else {
        ctx.status = 404;
        ctx.body = { success: false, message: '用户不存在' };
    }
};

module.exports = {
    login
};
6. controllers/userController.js - 用户操作控制器
const User = require('../models/user.js');

const getUsers = async (ctx) => {
    const users = await User.getAll();
    ctx.body = users;
};

const getUserById = async (ctx) => {
    const id = ctx.params.id;
    const user = await User.findById(id);
    if (user) {
        ctx.body = user;
    } else {
        ctx.status = 404;
        ctx.body = { message: '用户不存在' };
    }
};

const updateUser = async (ctx) => {
    const id = ctx.params.id;
    const updatedUser = ctx.request.body;
    const result = await User.update(id, updatedUser);
    if (result) {
        ctx.body = { message: '用户更新成功' };
    } else {
        ctx.status = 404;
        ctx.body = { message: '用户不存在,无法更新' };
    }
};

const deleteUser = async (ctx) => {
    const id = ctx.params.id;
    const result = await User.delete(id);
    if (result) {
        ctx.body = { message: '用户删除成功' };
    } else {
        ctx.status = 404;
        ctx.body = { message: '用户不存在,无法删除' };
    }
};

module.exports = {
    getUsers,
    getUserById,
    updateUser,
    deleteUser
};
7. middlewares/authMiddleware.js - token验证中间件
const jwt = require('jsonwebtoken');

const authMiddleware = async (ctx, next) => {
    const token = ctx.headers.authorization && ctx.headers.authorization
    .split(' ')[1];
    if (token) {
        try {
            const decoded = jwt.verify(token, 'your_secret_key');
            ctx.state.user = decoded;
            await next();
        } catch (error) {
            ctx.status = 401;
            ctx.body = { success: false, message: '无效的token' };
        }
    } else {
        ctx.status = 401;
        ctx.body = { success: false, message: '未提供token' };
    }
};

module.exports = authMiddleware;
8. routes/index.js - 首页路由
const Router = require('koa-router');
const router = new Router();

router.get('/', (ctx) => {
    ctx.body = '这是首页';
});

module.exports = router;
9. routes/user.js - 用户管理路由
const Router = require('koa-router');
const userController = require('../controllers/userController.js');
const authMiddleware = require('../middlewares/authMiddleware.js');
const router = new Router({ prefix: '/users' });

router.get('/', authMiddleware, userController.getUsers);
router.get('/:id', authMiddleware, userController.getUserById);
router.put('/:id', authMiddleware, userController.updateUser);
router.delete('/:id', authMiddleware, userController.deleteUser);

module.exports = router;
10. app.js - 应用入口文件
const Koa = require('koa');
const bodyParser = require('koa-bodyparser');
const cors = require('cors');
const indexRouter = require('./routes/index.js');
const userRouter = require('./routes/user.js');
const authController = require('./controllers/authController.js');
const Router = require('koa-router');
const app = new Koa();
const router = new Router();

// 使用cors中间件,允许跨域请求
app.use(cors());
// 应用中间件
app.use(bodyParser());

// 登录路由
router.post('/login', authController.login);

// 挂载首页路由
app.use(indexRouter.routes()).use(indexRouter.allowedMethods());
// 挂载用户管理路由
app.use(userRouter.routes()).use(userRouter.allowedMethods());
// 挂载总路由
app.use(router.routes()).use(router.allowedMethods());

app.listen(3000, () => {
    console.log('后端服务器启动,监听3000端口');
});

前端实现(Vue 3部分)

1. 创建Vue 3项目

使用@vue/cli创建项目(如果没有安装,先执行npm install -g @vue/cli):

vue create frontend

在创建项目过程中选择Vue 3相关选项。

2. 目录结构(主要部分)
frontend
|-- src
|   |-- components
|   |   |-- Login.vue
|   |   |-- Home.vue
|   |   |-- Detail.vue
|   |   |-- UserManagement.vue
|   |-- router
|   |   |-- index.js
|   |-- store
|   |   |-- index.js
|   |-- main.js
3. src/main.js - 入口文件
import { createApp } from 'vue';
import App from './App.vue';
import router from './router/index';
import store from './store/index';

createApp(App).use(router).use(store).mount('#app');
4. src/router/index.js - 路由配置
import { createRouter, createWebHistory } from 'vue-router';
import Login from '../components/Login.vue';
import Home from '../components/Home.vue';
import Detail from '../components/Detail.vue';
import UserManagement from '../components/UserManagement.vue';

const routes = [
    { path: '/', redirect: '/login' },
    { path: '/login', component: Login },
    { path: '/home', component: Home },
    { path: '/detail', component: Detail },
    { path: '/user-management', component: UserManagement }
];

const router = createRouter({
    history: createWebHistory(),
    routes
});

export default router;
5. src/components/Login.vue - 登录组件
<template>
    <div>
        <input v-model="username" placeholder="用户名" />
        <input type="password" v-model="password" placeholder="密码" />
        <button @click="login">登录</button>
    </div>
</template>

<script>
import axios from 'axios';

export default {
    data() {
        return {
            username: '',
            password: ''
        };
    },
    methods: {
        async login() {
            try {
                const response = await axios.post('http://localhost:3000/login', 
                {
                    username: this.username,
                    password: this.password
                });
                if (response.data.success) {
                    localStorage.setItem('token', response.data.token);
                    this.$router.push('/home');
                }
            } catch (error) {
                console.error('登录失败', error);
            }
        }
    }
};
</script>
6. src/components/Home.vue - 首页组件
<template>
    <div>这是首页</div>
</template>

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

<script>
export default {
    // 这里可以添加详情页相关逻辑
};
</script>
8. src/components/UserManagement.vue - 用户管理组件
<template>
    <div>
        <button v-if="user.role === 'admin'" @click="getUsers">获取用户列表</button>
        <!-- 这里可以添加更多用户管理相关的UI和操作 -->
    </div>
</template>

<script>
import axios from 'axios';

export default {
    data() {
        return {
            user: {},
            users: []
        };
    },
    created() {
        const token = localStorage.getItem('token');
        if (token) {
            axios.get('http://localhost:3000/users', {
                headers: {
                    Authorization: 'Bearer'+ token
                }
            }).then(response => {
                this.user = response.data;
            }).catch(error => {
                console.error('获取用户信息失败', error);
            });
        }
    },
    methods: {
        async getUsers() {
            const token = localStorage.getItem('token');
            try {
                const response = axios.get('http://localhost:3000/users', {
                    headers: {
                        Authorization: 'Bearer'+ token
                    }
                });
                this.users = (await response).data;
            } catch (error) {
                console.error('获取用户列表失败', error);
            }
        }
    }
};
</script>
9. src/store/index.js - Vuex存储(示例,可根据需要扩展)
import { createStore } from 'vuex';

const store = createStore({
    state() {
        return {
            user: null
        };
    },
    mutations: {
        setUser(state, user) {
            state.user = user;
        }
    },
    actions: {
        // 可以添加获取用户信息等相关的异步操作
    }
});

export default store;

注意事项

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

小结

以下是上述代码实例直接应用时需要注意的一些总结与建议:

一、安全相关

  1. 密码加密强度
    • 注意事项:在后端的 authController.js 中使用 bcryptjs 进行密码加密时,应合理设置加密成本因子(cost 参数)来增强密码安全性。默认的 cost 值可能不足以抵御强力的暴力破解攻击。
    • 实例剖析:例如,当前代码可能像这样使用 bcrypt 比较密码(简化示例):
    const isMatch = await bcrypt.compare(password, user.password);
    
    但没有设置 cost 参数,实际应用中可在密码创建或更新时明确设置合适的强度,像这样:
    const saltRounds = 12; // 合适的成本因子,可根据服务器性能等调整
    const hashedPassword = await bcrypt.hash(newPassword, saltRounds);
    
  2. Token 管理
    • 注意事项token 的生成与验证使用固定的 secret_key,且缺少刷新机制,同时存储和传输过程也需更安全的处理。
    • 实例剖析
      • authController.js 中生成 token 时:
      const token = jwt.sign({ id: user.id, username: user.username,
       role: user.role }, 'your_secret_key', { expiresIn: '1h' });
      
      这里的 your_secret_key 应配置为环境变量,避免硬编码在代码中导致泄露风险,比如在服务器启动时通过环境变量读取:
      const secretKey = process.env.JWT_SECRET_KEY;
      const token = jwt.sign({... }, secretKey, { expiresIn: '1h' });
      
      • 关于 token 刷新,当 token 快过期时,前端没有机制向后台请求新的 token 来无缝续期,用户体验不佳,需要实现类似的逻辑,例如设置定时器,在 token 有效期的一定时间(如还剩 10 分钟过期时)向专门的 token 刷新接口发起请求获取新 token
  3. 数据库连接安全
    • 注意事项:数据库连接配置中用户名、密码等信息硬编码不安全,并且缺少完善的错误处理和连接池管理。
    • 实例剖析:在 config/db.js 中:
      const pool = mysql.createPool({
          host: 'localhost',
          user: 'root',
          password: 'your_password',
          database: 'your_database_name',
          //... 其他配置
      });
      
      应改为通过环境变量来设置这些敏感信息,如:
      const pool = mysql.createPool({
          host: process.env.DB_HOST,
          user: process.env.DB_USER,
          password: process.env.DB_PASSWORD,
          database: process.env.DB_NAME,
          //... 其他配置
      });
      
      同时,对于数据库操作可能出现的错误(如连接失败、查询超时等),目前只是简单返回结果,没有针对性的错误处理,例如在 models/user.js 中查询用户时,如果数据库连接出现问题,应进行更好的错误捕获和处理,像这样:
      static async findByUsername(username) {
          try {
              const [rows] = await pool.query('SELECT * FROM users 
              WHERE username =?', [username]);
              return rows[0];
          } catch (error) {
              console.error('数据库查询用户出错:', error);
              throw new Error('数据库查询用户时遇到问题,请稍后再试');
          }
      }
      

二、错误处理

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

三、代码结构与可维护性

  1. 分层与模块化
    • 注意事项:随着业务扩展,目前代码的分层不够清晰,业务逻辑分散在控制器和模型中,不利于维护和扩展,可添加服务层来封装业务逻辑。
    • 实例剖析:例如用户注册功能,如果后续需要添加更多复杂逻辑(如验证手机号格式、发送注册短信验证码等),目前这些逻辑可能会散落在控制器里的注册方法中,更好的做法是创建 userService.js 服务层文件,在里面封装注册相关的完整逻辑,像这样:
    const User = require('./models/user.js');
    const bcrypt = require('bcryptjs');
    const register = async (userData) => {
        // 验证手机号等额外逻辑(此处省略具体实现)
        const hashedPassword = await bcrypt.hash(userData.password, 10);
        const newUser = {
            username: userData.username,
            password: hashedPassword,
            role: userData.role,
            menus: userData.menus
        };
        return User.create(newUser);
    };
    module.exports = {
        register
    };
    
    然后在 authController.js 中调用这个服务层方法来进行注册操作,使得业务逻辑更加清晰,便于后续维护和扩展。
  2. 数据库操作可移植性
    • 注意事项:直接写 SQL 语句进行数据库操作,后期若更换数据库(如从 MySQL 换为 PostgreSQL),改动成本大,可考虑引入 ORM 框架。
    • 实例剖析:当前在 models/user.js 中有很多原生 SQL 查询语句,如:
    static async findByUsername(username) {
        const [rows] = await pool.query
        ('SELECT * FROM users WHERE username =?', [username]);
        return rows[0];
    }
    
    若使用 Sequelize(一种常见 ORM 框架),代码可以改为:
    const { Model, DataTypes } = require('sequelize');
    const sequelize = require('../config/database');
    
    class User extends Model {}
    
    User.init({
        id: {
            type: DataTypes.INTEGER,
            autoIncrement: true,
            primaryKey: true
        },
        username: {
            type: DataTypes.STRING,
            allowNull: false,
            unique: true
        },
        password: {
            type: DataTypes.STRING,
            allowNull: false
        },
        role: {
            type: DataTypes.STRING,
            allowNull: false
        },
        menus: {
            type: DataTypes.TEXT
        },
        created_at: {
            type: DataTypes.DATE,
            defaultValue: DataTypes.NOW
        },
        updated_at: {
            type: DataTypes.DATE,
            defaultValue: DataTypes.NOW,
            onUpdate: DataTypes.NOW
        }
    }, {
        sequelize,
        modelName: 'User'
    });
    
    module.exports = User;
    
    这样后续切换数据库时,只需调整 Sequelize 的配置,而不用大量修改具体的数据库操作语句。

四、权限控制

  1. 细粒度权限
    • 注意事项:当前基于用户角色和菜单的权限控制较粗,实际可能需要更细粒度,比如对具体功能按钮等操作进行权限管控。
    • 实例剖析:在前端 UserManagement.vue 组件中,目前只是简单根据角色判断是否显示获取用户列表按钮:
    <button v-if="user.role === 'admin'" @click="getUsers">获取用户列表</button>
    
    但可能对于“删除用户”这个操作,不仅要判断角色是 admin,还需要后端进一步返回针对每个用户是否有删除权限的具体标识,前端根据这个标识来决定按钮是否可用,后端则在对应的删除用户接口处详细校验这个权限,比如在 userController.jsdeleteUser 方法中添加更细的权限判断逻辑:
    const deleteUser = async (ctx) => {
        const id = ctx.params.id;
        const user = ctx.state.user;
        if (user.role === 'admin' && user.permissions.includes('delete_user')) {
            const result = await User.delete(id);
            if (result) {
                ctx.body = { message: '用户删除成功' };
            } else {
                ctx.status = 404;
                ctx.body = { message: '用户不存在,无法删除' };
            }
        } else {
            ctx.status = 403;
            ctx.body = { message: '您没有权限执行此操作' };
        }
    };
    
  2. 权限数据校验
    • 注意事项:后端没有对前端传递的权限相关数据严格校验,存在前端篡改数据越权访问风险。
    • 实例剖析:当前前端获取用户信息后可以拿到权限相关数据(如角色、菜单权限等),若前端恶意篡改这些数据再发起请求,后端没有有效校验机制就可能导致越权访问。比如在后端的 authMiddleware.js 中验证 token 后获取用户信息时,可以增加对权限数据格式、范围等的校验逻辑,确保其合法性,像这样:
    const authMiddleware = async (ctx, next) => {
        const token = ctx.headers.authorization && ctx.headers.authorization
        .split(' ')[1];
        if (token) {
            try {
                const decoded = jwt.verify(token,'secret_key');
                // 校验权限相关字段是否符合预期格式和范围
                if (typeof decoded.role ==='string' && 
                Array.isArray(decoded.menus)) {
                    ctx.state.user = decoded;
                    await next();
                } else {
                    ctx.status = 401;
                    ctx.body = { success: false, message: '权限数据格式错误' };
                }
            } catch (error) {
                ctx.status = 401;
                ctx.body = { success: false, message: '无效的token' };
            }
        } else {
            ctx.status = 401;
            ctx.body = { success: false, message: '未提供token' };
        }
    };
    

五、前端相关

  1. Token 存储安全

    • 注意事项:前端将 token 存储在 localStorage 容易遭受 XSS 攻击,应采用更安全的存储方式。
    • 实例剖析:在 Login.vue 组件中登录成功后这样存储 token
    localStorage.setItem('token', response.data.token);
    

    可以考虑使用 cookie 并设置 httpOnly 属性来存储 token,在后端设置 cookie 相关属性(例如通过 koa 的中间件来设置),让前端无法通过脚本访问到 token,增强安全性。

  2. 用户输入验证

    • 注意事项:前端对用户输入验证简单,容易被利用发起恶意攻击,应加强输入框的验证。
    • 实例剖析:在 Login.vue 组件中,用户名和密码输入框没有限制输入内容,像这样:
    <input v-model="username" placeholder="用户名" />
    <input type="password" v-model="password" placeholder="密码" />
    

    可以添加 v-validate(使用 vee-validate 等验证库)等方式来限制输入长度、格式等,例如:

    <input v-model="username" placeholder="用户名" 
    v-validate="'required|min:3|max:20'" />
    <input type="password" v-model="password" 
    placeholder="密码" v-validate="'required|min:6|max:20'" />
    

    并根据验证结果给出相应提示,防止不合理的输入传递到后端。

  3. 接口请求优化与统一处理

    • 注意事项:前端使用 axios 进行接口请求时,缺少统一的拦截处理,导致代码冗余且不利于维护,同时存在重复请求等性能问题。
    • 实例剖析:在多个组件(如 UserManagement.vue)中都有发起获取用户相关信息的请求,每次都要写类似这样的代码:
    const token = localStorage.getItem('token');
    try {
        const response = axios.get('http://localhost:3000/users', {
            headers: {
                Authorization: 'Bearer'+ token
            }
        });
        this.users = (await response).data;
    } catch (error) {
        console.error('获取用户列表失败', error);
    }
    

    可以创建 axios 实例并设置统一的请求拦截器(添加请求头、处理加载状态等)和响应拦截器(处理错误、统一解析数据等),例如在 src/api/index.js 创建:

    import axios from 'axios';
    
    const instance = axios.create({
        baseURL: 'http://localhost:3000'
    });
    
    instance.interceptors.request.use((config) => {
        const token = localStorage.getItem('token');
        if (token) {
            config.headers.Authorization = `Bearer ${token}`;
        }
        return config;
    }, (error) => {
        return Promise.reject(error);
    });
    
    instance.interceptors.response.use((response) => {
        return response.data;
    }, (error) => {
        console.error('接口请求出错:', error);
        return Promise.reject(error);
    });
    
    export default instance;
    

    然后在组件中直接使用这个 axios 实例进行请求,减少重复代码,也方便对请求进行统一管理和优化。

总之,上述代码示例要直接应用到实际生产环境,需要从安全、错误处理、代码结构、权限控制以及前端相关等多方面进行完善和优化,以保障应用的稳定、安全和可维护性。

补充(前端将 token 存储在 localStorage 容易遭受 XSS 攻击,应采用更安全的存储方式,具体如下:)

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

// 应用中间件
app.use(cookie());

// 假设在登录成功后设置token到Cookie
const login = async (ctx) => {
    //... 登录验证逻辑
    const token = jwt.sign({... },'secret_key', { expiresIn: '1h' });
    ctx.cookies.set('token', token, {
        httpOnly: true,
        // 还可以设置其他属性,如maxAge(过期时间,单位为毫秒)、path(Cookie生效的路径)等
        maxAge: 3600 * 1000, 
        path: '/'
    });
    ctx.body = { success: true };
};
  • 前端注意事项:由于 HttpOnly 的特性,前端无法通过 document.cookie 等方式获取 token,因此在发送需要认证的请求时,浏览器会自动将带有 HttpOnly 属性的 Cookie 包含在请求头中(前提是请求的域名、路径等符合 Cookie 设置的要求)。但要注意,这种方式在跨域场景下可能需要额外配置(如设置 withCredentials 属性为 true),同时要确保后端正确处理跨域的 Cookie
  1. Encrypted LocalStorage
    • 原理:在将 token 存储到 localStorage 之前,使用加密算法对其进行加密。这样即使攻击者能够访问 localStorage,获取到的也是加密后的内容,无法直接使用。在需要使用 token 时,再通过解密算法还原。
    • 示例代码(使用crypto-js库进行加密和解密)
    • 首先安装 crypto - js
npm install crypto-js
  • 存储 token(在登录成功后,假设在一个 Vue 组件中):
import CryptoJS from 'crypto-js';
const SECRET_KEY = 'your_secret_key'; // 用于加密和解密的密钥,要妥善保管

const storeToken = (token) => {
    const encryptedToken = CryptoJS.AES.encrypt(token, SECRET_KEY).toString();
    localStorage.setItem('encrypted_token', encryptedToken);
};
  • 获取和使用 token(在需要发送认证请求的组件方法中):
const getToken = () => {
    const encryptedToken = localStorage.getItem('encrypted_token');
    if (encryptedToken) {
        const bytes = CryptoJS.AES.decrypt(encryptedToken, SECRET_KEY);
        const originalToken = bytes.toString(CryptoJS.enc.Utf8);
        return originalToken;
    }
    return null;
};
  • 注意事项:加密密钥的安全性至关重要,不能在代码中硬编码,应该像存储 tokensecret_key 一样,通过环境变量等方式配置。同时,加密和解密操作可能会对性能产生一定的影响,尤其是在频繁存储和获取 token 的场景下。
  1. Memory Storage(In - Memory Session)
    • 原理:将 token 存储在内存中,例如在 Vue 应用中,可以使用一个全局的变量(如 Vuex 状态管理中的状态)或者一个单例对象来存储 token。这种方式下,token 不会持久化存储在本地存储介质中,只要页面关闭或者应用退出,token 就会消失,因此可以避免一些存储层面的安全风险。
    • 示例(使用Vuex存储)
    • src/store/index.js 中:
import { createStore } from 'vuex';

const store = createStore({
    state() {
        return {
            token: null
        };
    },
    mutations: {
        setToken(state, token) {
            state.token = token;
        }
    },
    actions: {
        // 例如在登录成功后设置token
        loginSuccess({ commit }, token) {
            commit('setToken', token);
        }
    }
});

export default store;
  • 在登录组件(如 Login.vue)中:
import { mapActions } from 'vuex';
export default {
    methods: {
      ...mapActions(['loginSuccess']),
        async login() {
            try {
                const response = await axios.post('http://localhost:3000/login', {
                    //... 登录参数
                });
                if (response.data.success) {
                    this.loginSuccess(response.data.token);
                    //... 其他逻辑,如路由跳转
                }
            } catch (error) {
                console.error('登录失败', error);
            }
        }
    }
};
  • 注意事项:这种方式的缺点是,如果用户刷新页面或者意外关闭标签页后重新打开,用户可能需要重新登录获取 token,会影响用户体验。为了缓解这个问题,可以结合其他存储方式(如前面提到的加密 localStorage)来在一定程度上恢复用户状态,但这也增加了复杂性和潜在的安全风险。同时,在多标签页或者多窗口场景下,内存中的存储方式需要考虑如何在不同标签页之间同步 token 状态等问题。

http://www.kler.cn/a/400534.html

相关文章:

  • MySQL —— MySQL索引介绍、索引数据结构、聚集索引和辅助索引、索引覆盖
  • VScode使用Batch Runner插件在终端运行bat文件
  • Spring 与 Spring MVC 与 Spring Boot三者之间的区别与联系
  • 前端处理input框只能输入带小数点的数字
  • 【汇编】c++游戏开发
  • 【linux】如何扩展磁盘容量(VMware虚拟机)-转载
  • DAY66||Floyd 算法精讲 |A * 算法精讲 (A star算法)|最短路算法总结篇|图论总结
  • PyTorch 与 TensorFlow 模型搭建的区别
  • 前端处理input框只能输入带小数点的数字
  • gin源码阅读(1)URL中的参数是如何解析的?
  • FastApi学习第二天:Pydantic对数据的校验和Form表单数据
  • 力扣题解661 图片平滑器
  • 三周精通FastAPI:42 手动运行服务器 - Uvicorn Gunicorn with Uvicorn
  • 群控系统服务端开发模式-应用开发-前端管理员功能开发
  • BLE 蓝牙客户端和服务器连接
  • 纯前端实现语音文字互转
  • 大模型实操练习二、文心大模型API使用方法(入门阶段)
  • 离散数学笔记
  • 【ASR技术】WhisperX安装使用
  • 【论文阅读】InstructPix2Pix: Learning to Follow Image Editing Instructions
  • 键盘上打出反引号符号(´),即单个上标的撇号(这个符号与反引号 ` 不同,反引号通常位于键盘的左上角)
  • DBeaver MACOS 安装 并连接到docker安装的mysql
  • Android 开发与救砖工具介绍
  • Fisher矩阵和Hessian矩阵的关系:证明Fisher为负对数似然函数的Hessian的期望
  • LeetCode 2816.翻倍以链表形式表示的数字
  • 消息队列原理面试题及参考答案