博客 / 詳情

返回

為什麼永遠不要讓前端直接連接數據庫

在現代Web開發中,安全性是至關重要的考慮因素。一個常見的反模式就是讓前端應用直接連接數據庫。本文將深入探討為什麼這種做法存在嚴重安全隱患,以及正確的架構模式應該如何設計。

為什麼前端絕不應該直接連接數據庫?

1. 安全風險暴露

當你的前端代碼(如Vue.js、React或Angular應用)直接連接數據庫時,意味着數據庫憑證和連接信息必須存儲在客户端代碼中。這會帶來以下風險:

// ❌ 錯誤示例 - 絕對不要這樣做!
const dbConfig = {
  host: 'your-database-host.com',
  user: 'admin',
  password: 'your-secret-password', // 密碼暴露給所有用户!
  database: 'production_db'
};

// 這些信息可以通過瀏覽器開發者工具輕易獲取

2. 完整的數據庫訪問權限

前端直接連接數據庫通常意味着給予客户端過多權限:

  • 可能執行刪除操作
  • 可以讀取敏感數據
  • 能夠修改關鍵業務邏輯

正確的架構模式

API網關模式(推薦)

[Vue.js 前端] ←→ [RESTful API/GraphQL] ←→ [後端服務] ←→ [數據庫]

實施示例

1. 後端API服務(Node.js + Express)

// server.js
const express = require('express');
const cors = require('cors');
const rateLimit = require('express-rate-limit');

const app = express();

// 安全中間件
app.use(cors({
  origin: process.env.FRONTEND_URL,
  credentials: true
}));

// 速率限制防止濫用
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15分鐘
  max: 100 // 限制每個IP 100次請求
});
app.use(limiter);

// 數據庫連接(僅服務器端)
const mysql = require('mysql2/promise');
const db = mysql.createPool({
  host: process.env.DB_HOST,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_NAME
});

// 安全的API端點
app.get('/api/users/:id', async (req, res) => {
  try {
    const [rows] = await db.execute(
      'SELECT id, name, email FROM users WHERE id = ?',
      [req.params.id]
    );
  
    if (rows.length === 0) {
      return res.status(404).json({ error: 'User not found' });
    }
  
    // 只返回必要的字段,過濾敏感信息
    res.json(rows[0]);
  } catch (error) {
    console.error(error);
    res.status(500).json({ error: 'Internal server error' });
  }
});

app.listen(3000);

2. Vue.js前端調用

<!-- UserComponent.vue -->
<template>
  <div v-if="loading">Loading...</div>
  <div v-else-if="user">
    <h2>{{ user.name }}</h2>
    <p>{{ user.email }}</p>
  </div>
  <div v-else-if="error">{{ error }}</div>
</template>

<script>
import axios from 'axios';

export default {
  name: 'UserComponent',
  data() {
    return {
      user: null,
      loading: false,
      error: null
    };
  },

  async mounted() {
    await this.fetchUser();
  },

  methods: {
    async fetchUser() {
      this.loading = true;
      this.error = null;
    
      try {
        // ✅ 通過安全的API端點獲取數據
        const response = await axios.get(`/api/users/${this.userId}`);
        this.user = response.data;
      } catch (error) {
        this.error = error.response?.data?.error || 'Failed to fetch user';
        console.error('API Error:', error);
      } finally {
        this.loading = false;
      }
    }
  }
};
</script>

高級安全措施

1. 身份驗證和授權

// auth.middleware.js
const jwt = require('jsonwebtoken');

const authenticateToken = (req, res, next) => {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1];

  if (!token) {
    return res.sendStatus(401);
  }

  jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
    if (err) {
      return res.sendStatus(403);
    }
    req.user = user;
    next();
  });
};

// 在路由中使用
app.get('/api/users/:id', authenticateToken, async (req, res) => {
  // 確保用户只能訪問自己的數據
  if (req.params.id !== req.user.id.toString()) {
    return res.status(403).json({ error: 'Access denied' });
  }

  // 執行數據庫查詢...
});

2. 輸入驗證和清理

// validation.middleware.js
const { body, validationResult } = require('express-validator');

const validateUserUpdate = [
  body('email').isEmail().normalizeEmail(),
  body('name').trim().isLength({ min: 2, max: 50 }),

  (req, res, next) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ 
        error: 'Validation failed',
        details: errors.array()
      });
    }
    next();
  }
];

app.put('/api/users/:id', authenticateToken, validateUserUpdate, async (req, res) => {
  // 處理更新邏輯...
});

3. 環境變量管理

# .env.production
DB_HOST=your-production-db-host.com
DB_USER=restricted_user
DB_PASSWORD=strong-password-here
DB_NAME=your_app_db
JWT_SECRET=your-super-secret-jwt-key
FRONTEND_URL=https://yourapp.com

監控和日誌

// logger.js
const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.json()
  ),
  transports: [
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' })
  ]
});

// 在API中使用
app.use((req, res, next) => {
  logger.info(`${req.method} ${req.path}`, {
    ip: req.ip,
    userAgent: req.get('User-Agent')
  });
  next();
});

總結

永遠不要讓前端直接連接數據庫是一個基本的安全原則。正確的做法是:

  1. 始終使用API層作為前端和數據庫之間的中介
  2. 實施嚴格的認證和授權機制
  3. 進行輸入驗證和數據清理
  4. 使用環境變量管理敏感配置
  5. 實現適當的監控和日誌記錄
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.