SQL 注入与前端安全

理解 SQL 注入原理及前端在防护中的角色

问题

SQL 注入是常见的安全漏洞,前端在其中扮演什么角色?如何配合后端进行防护?

解答

SQL 注入原理

SQL 注入是攻击者通过输入恶意 SQL 代码,篡改原有查询逻辑的攻击方式。

-- 正常查询
SELECT * FROM users WHERE username = 'admin' AND password = '123456'

-- 注入攻击:输入用户名为 admin'--
SELECT * FROM users WHERE username = 'admin'--' AND password = '任意值'
-- 密码验证被注释掉,直接登录成功

前端的角色

前端是第一道防线,但不是安全保障。前端验证可以被绑过(禁用 JS、直接调接口)。

// 前端输入验证 - 只能提升用户体验,不能保证安全
function validateInput(input) {
  // 检查危险字符
  const dangerousChars = /['";\\-]/g;
  if (dangerousChars.test(input)) {
    return { valid: false, message: '输入包含非法字符' };
  }
  
  // 检查常见注入关键词
  const sqlKeywords = /\b(SELECT|INSERT|UPDATE|DELETE|DROP|UNION|OR|AND)\b/gi;
  if (sqlKeywords.test(input)) {
    return { valid: false, message: '输入包含非法关键词' };
  }
  
  return { valid: true };
}

// 使用示例
const userInput = document.getElementById('username').value;
const result = validateInput(userInput);
if (!result.valid) {
  alert(result.message);
  return;
}

前端应该做的事

// 1. 输入长度限制
<input type="text" maxlength="50" />

// 2. 输入类型约束
<input type="email" />  // 邮箱格式
<input type="number" /> // 只能输入数字

// 3. 正则验证
function validateUsername(username) {
  // 只允许字母、数字、下划线
  return /^[a-zA-Z0-9_]{3,20}$/.test(username);
}

// 4. 转义特殊字符(展示用,不是安全措施)
function escapeHtml(str) {
  const map = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#039;'
  };
  return str.replace(/[&<>"']/g, char => map[char]);
}

真正的防护在后端

// Node.js + MySQL 示例

// ❌ 错误:字符串拼接
const query = `SELECT * FROM users WHERE username = '${username}'`;

// ✅ 正确:参数化查询
const query = 'SELECT * FROM users WHERE username = ?';
connection.query(query, [username], (err, results) => {
  // 处理结果
});

// ✅ 正确:使用 ORM(如 Sequelize)
const user = await User.findOne({
  where: { username: username }
});

完整的登录表单示例

<form id="loginForm">
  <input 
    type="text" 
    id="username" 
    maxlength="20" 
    pattern="[a-zA-Z0-9_]+"
    required
  />
  <input 
    type="password" 
    id="password" 
    maxlength="50"
    required
  />
  <button type="submit">登录</button>
</form>

<script>
document.getElementById('loginForm').addEventListener('submit', async (e) => {
  e.preventDefault();
  
  const username = document.getElementById('username').value.trim();
  const password = document.getElementById('password').value;
  
  // 前端基本验证
  if (!/^[a-zA-Z0-9_]{3,20}$/.test(username)) {
    alert('用户名格式不正确');
    return;
  }
  
  // 发送请求,安全由后端保证
  const response = await fetch('/api/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ username, password })
  });
  
  const result = await response.json();
  // 处理登录结果
});
</script>

关键点

  • 前端验证可被绑过:攻击者可以禁用 JS 或直接调用 API,前端验证只是用户体验优化
  • 后端必须用参数化查询:这是防止 SQL 注入的根本方法,永远不要拼接 SQL 字符串
  • 前端职责是过滤和提示:限制输入格式、长度,给用户友好的错误提示
  • 纵深防御:前端验证 + 后端参数化查询 + 最小权限原则 + 输入白名单
  • 永远不信任用户输入:无论前端还是后端,都要对输入进行验证