0%

安全与最佳实践

安全与最佳实践

Web 安全是前端开发中至关重要的部分。不安全的 HTML 代码可能导致跨站脚本攻击(XSS)、点击劫持、数据泄露等严重安全问题。本文档将介绍 HTML 开发中的安全最佳实践,包括安全响应头、XSS 防护、表单安全和外链安全等内容。

1. 安全响应头(Security Headers)

HTTP 安全响应头是服务器发送给浏览器的安全指令,用于防止各种类型的攻击。这些头部应该在服务器端配置,但了解它们对于前端开发者同样重要。

1.1 Content-Security-Policy (CSP)

Content-Security-Policy(内容安全策略)是最重要的安全头部之一,用于防止 XSS 攻击。

基本语法

1
Content-Security-Policy: directive1 value1; directive2 value2;

常用指令

指令 说明 示例
default-src 所有资源类型的默认策略 default-src 'self'
script-src 控制脚本来源 script-src 'self' 'unsafe-inline'
style-src 控制样式表来源 style-src 'self' https://fonts.googleapis.com
img-src 控制图片来源 img-src 'self' data: https:
font-src 控制字体来源 font-src 'self' https://fonts.gstatic.com
connect-src 控制 AJAX/WebSocket 连接 connect-src 'self' https://api.example.com
frame-src 控制 iframe 来源 frame-src 'self' https://trusted-site.com
object-src 控制 object/embed/applet object-src 'none'
media-src 控制 audio/video 来源 media-src 'self'
form-action 控制表单提交目标 form-action 'self'
base-uri 控制 <base> 标签的 href base-uri 'self'
frame-ancestors 控制页面是否可被嵌入 iframe frame-ancestors 'none'
report-uri 违规报告 URL report-uri /csp-report

常用值

说明
'self' 允许同源资源
'unsafe-inline' 允许内联脚本/样式(不安全)
'unsafe-eval' 允许 eval() 等动态代码执行(不安全)
'none' 禁止所有
https: 允许所有 HTTPS 来源
data: 允许 data: URL
*.example.com 允许特定域名

CSP 示例

严格的 CSP(推荐)

1
2
3
4
5
6
7
8
9
10
11
Content-Security-Policy: 
default-src 'self';
script-src 'self' 'unsafe-inline' https://cdn.example.com;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
img-src 'self' data: https:;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.example.com;
frame-src 'none';
object-src 'none';
base-uri 'self';
form-action 'self';

在 HTML meta 标签中使用

1
2
3
4
5
6
7
<meta http-equiv="Content-Security-Policy" 
content="
default-src 'self';
script-src 'self' 'unsafe-inline' https://cdn.example.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
">

注意:meta 标签的 CSP 有一些限制,推荐在服务器端设置。

渐进式实施 CSP

如果现有网站大量使用内联脚本,可以逐步实施:

第一步:报告模式(只报告,不阻止)

1
2
3
4
Content-Security-Policy-Report-Only: 
default-src 'self';
script-src 'self';
report-uri /csp-report;

第二步:启用 CSP(真正阻止违规)

1
2
3
4
Content-Security-Policy: 
default-src 'self';
script-src 'self' 'unsafe-inline';
report-uri /csp-report;

第三步:逐步收紧(移除 unsafe-inline

1
2
3
4
Content-Security-Policy: 
default-src 'self';
script-src 'self' 'nonce-随机字符串';
report-uri /csp-report;

Nonce 和 Hash

如果必须使用内联脚本,可以使用 nonce 或 hash:

使用 Nonce

1
Content-Security-Policy: script-src 'self' 'nonce-EDNnf03nceIOfn39fn3e9h3sdfa';
1
2
3
4
<script nonce="EDNnf03nceIOfn39fn3e9h3sdfa">
// 只有带有正确 nonce 的脚本才会执行
console.log('安全的内联脚本');
</script>

使用 Hash

1
Content-Security-Policy: script-src 'self' 'sha256-abc123...';

浏览器会计算脚本的 hash 值,只有匹配的脚本才会执行。

1.2 X-Frame-Options

防止页面被嵌入到 iframe 中(点击劫持防护)。

1
X-Frame-Options: DENY

可选值

  • DENY:完全禁止被嵌入
  • SAMEORIGIN:只允许同源页面嵌入
  • ALLOW-FROM uri:允许指定 URI 嵌入(已废弃)

推荐配置

1
X-Frame-Options: DENY

或者使用更现代的 CSP 方式:

1
Content-Security-Policy: frame-ancestors 'none'

注意:如果使用了 CSP 的 frame-ancestorsX-Frame-Options 会被忽略。

1.3 X-Content-Type-Options

防止浏览器 MIME 类型嗅探。

1
X-Content-Type-Options: nosniff

作用:强制浏览器使用服务器指定的 Content-Type,防止将文本文件当作脚本执行。

推荐配置

1
X-Content-Type-Options: nosniff

1.4 Referrer-Policy

控制浏览器发送 Referer 头的信息。

1
Referrer-Policy: strict-origin-when-cross-origin

可选值

说明
no-referrer 不发送 Referer
no-referrer-when-downgrade 降级(HTTPS→HTTP)时不发送(默认)
origin 只发送源(协议+域名)
origin-when-cross-origin 跨域时只发送源
same-origin 同源时发送完整 URL
strict-origin 只发送源,降级时不发送
strict-origin-when-cross-origin 同源发送完整,跨域发送源,降级时不发送(推荐)
unsafe-url 始终发送完整 URL(不安全)

推荐配置

1
Referrer-Policy: strict-origin-when-cross-origin

在 HTML 中设置

1
<meta name="referrer" content="strict-origin-when-cross-origin">

1.5 Permissions-Policy(原 Feature-Policy)

控制浏览器功能的使用权限。

1
Permissions-Policy: geolocation=(), microphone=(), camera=(), payment=(self)

常用指令

功能 说明
geolocation 地理位置
microphone 麦克风
camera 摄像头
payment 支付 API
usb USB 设备
fullscreen 全屏
autoplay 自动播放

示例

1
2
3
4
5
Permissions-Policy: 
geolocation=(),
microphone=(),
camera=(),
payment=(self "https://payment.example.com")

在 HTML 中设置

1
2
<meta http-equiv="Permissions-Policy" 
content="geolocation=(), microphone=(), camera=()">

1.6 Strict-Transport-Security (HSTS)

强制使用 HTTPS 连接。

1
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

参数说明

  • max-age:有效期(秒)
  • includeSubDomains:包含子域名
  • preload:加入浏览器 HSTS 预加载列表

推荐配置

1
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

1.7 完整的安全响应头配置示例

Nginx 配置示例

1
2
3
4
5
6
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https://fonts.gstatic.com; connect-src 'self' https://api.example.com; frame-ancestors 'none'; object-src 'none'; base-uri 'self'; form-action 'self';" always;
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=(self)" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

Apache 配置示例

1
2
3
4
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline';"
Header always set X-Frame-Options "DENY"
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"

2. XSS(跨站脚本攻击)防护

XSS 是最常见的 Web 安全漏洞之一,攻击者通过在网页中注入恶意脚本,窃取用户信息或执行恶意操作。

2.1 XSS 攻击类型

反射型 XSS

恶意脚本通过 URL 参数等方式反射到页面中。

攻击示例

1
2
3
4
5
<!-- 不安全的代码 -->
<div>欢迎, <?php echo $_GET['name']; ?></div>

<!-- 攻击者构造的 URL -->
http://example.com/?name=<script>alert(document.cookie)</script>

存储型 XSS

恶意脚本被存储到数据库中,每次访问时都会执行。

攻击示例

1
2
3
4
5
<!-- 用户输入的评论 -->
<script>alert('XSS')</script>

<!-- 直接输出到页面(不安全) -->
<div><?php echo $comment; ?></div>

DOM 型 XSS

通过修改 DOM 结构导致脚本执行。

攻击示例

1
2
3
4
5
// 不安全的代码
document.getElementById('output').innerHTML = location.hash.substring(1);

// 攻击 URL
http://example.com/#<img src=x onerror=alert('XSS')>

2.2 XSS 防护措施

1. 输入验证和过滤

服务器端验证

1
2
3
4
5
6
7
8
// 过滤 HTML 标签
$input = strip_tags($userInput);

// 使用白名单验证
$input = filter_var($userInput, FILTER_SANITIZE_STRING);

// 转义特殊字符
$input = htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8');

客户端验证(仅作辅助,不能依赖):

1
2
3
4
5
6
7
8
function sanitizeInput(input) {
const div = document.createElement('div');
div.textContent = input;
return div.innerHTML;
}

// 使用
const safeInput = sanitizeInput(userInput);

2. 输出编码

HTML 实体编码

1
2
<!-- 转义特殊字符 -->
&lt;script&gt;alert('XSS')&lt;/script&gt;

JavaScript 中的编码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 使用 textContent 而不是 innerHTML
element.textContent = userInput; // 安全

// 如果必须使用 innerHTML,先编码
function escapeHtml(text) {
const map = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
};
return text.replace(/[&<>"']/g, m => map[m]);
}

URL 编码

1
2
// 在 URL 中使用用户输入
const safeUrl = encodeURIComponent(userInput);

3. 使用安全的 API

避免使用危险的 DOM API

1
2
3
4
5
6
7
8
9
// ❌ 不安全
element.innerHTML = userInput;
document.write(userInput);
eval(userInput);
setTimeout(userInput, 100);

// ✅ 安全
element.textContent = userInput;
element.setAttribute('data-value', userInput);

4. Content Security Policy (CSP)

如前所述,CSP 是最有效的 XSS 防护措施之一。

1
Content-Security-Policy: script-src 'self'; object-src 'none';

防止 JavaScript 访问敏感 Cookie。

1
Set-Cookie: sessionid=abc123; HttpOnly; Secure; SameSite=Strict

2.3 XSS 防护最佳实践

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">

<!-- CSP 策略 -->
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline';">

<title>安全的页面</title>
</head>
<body>
<!-- 使用 textContent 而不是 innerHTML -->
<div id="output"></div>

<script>
// ✅ 安全:使用 textContent
function displayUserInput(input) {
const output = document.getElementById('output');
output.textContent = input;
}

// ✅ 安全:如果需要 HTML,使用 DOM API
function displayHtml(htmlString) {
const output = document.getElementById('output');
const temp = document.createElement('div');
temp.textContent = htmlString; // 先设置为文本
// 然后使用安全的 DOM 方法构建 HTML
output.appendChild(temp);
}

// ❌ 不安全:直接使用 innerHTML
// output.innerHTML = userInput;

// ✅ 安全:URL 参数编码
function getUrlParam(name) {
const params = new URLSearchParams(window.location.search);
return params.get(name); // URLSearchParams 自动解码
}
</script>
</body>
</html>

3. 表单安全

表单是用户输入的主要入口,也是最容易受到攻击的地方。

3.1 输入验证

HTML5 验证属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<form>
<!-- 必填字段 -->
<input type="text" name="username" required>

<!-- 模式验证 -->
<input type="email" name="email" required>
<input type="tel" name="phone" pattern="[0-9]{10,11}">

<!-- 长度限制 -->
<input type="text" name="title" maxlength="100" minlength="5">

<!-- 数值范围 -->
<input type="number" name="age" min="18" max="100">

<!-- 文件类型和大小 -->
<input type="file" accept="image/*" max="5242880"> <!-- 5MB -->

<button type="submit">提交</button>
</form>

客户端验证(JavaScript)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<form id="myForm">
<input type="text" name="username" id="username" required>
<input type="email" name="email" id="email" required>
<button type="submit">提交</button>
</form>

<script>
document.getElementById('myForm').addEventListener('submit', function(e) {
e.preventDefault();

const username = document.getElementById('username').value;
const email = document.getElementById('email').value;

// 验证用户名
if (!/^[a-zA-Z0-9_]{3,20}$/.test(username)) {
alert('用户名必须是3-20个字母、数字或下划线');
return;
}

// 验证邮箱
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
alert('请输入有效的邮箱地址');
return;
}

// 验证通过,提交表单
this.submit();
});
</script>

重要提醒:客户端验证只能提升用户体验,不能作为唯一的安全措施,服务器端验证是必需的。

3.2 CSRF(跨站请求伪造)防护

CSRF Token

在表单中添加 CSRF token,服务器验证 token 的有效性。

1
2
3
4
5
6
7
<form method="POST" action="/submit">
<!-- CSRF Token -->
<input type="hidden" name="csrf_token" value="随机生成的token">

<input type="text" name="data">
<button type="submit">提交</button>
</form>

JavaScript 获取 token

1
2
3
4
5
6
7
8
9
10
11
12
// 从 Cookie 或 meta 标签获取 token
const token = document.querySelector('meta[name="csrf-token"]').content;

// 在 AJAX 请求中包含 token
fetch('/api/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': token
},
body: JSON.stringify({ data: 'value' })
});
1
Set-Cookie: sessionid=abc123; SameSite=Strict; Secure; HttpOnly

SameSite 值

  • Strict:完全禁止跨站发送 Cookie
  • Lax:GET 请求允许跨站,POST 禁止(推荐)
  • None:允许跨站(需要 Secure)

3.3 密码安全

密码输入字段

1
2
3
4
<input type="password" name="password" 
minlength="8"
required
autocomplete="new-password">

密码强度验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function validatePassword(password) {
const minLength = 8;
const hasUpperCase = /[A-Z]/.test(password);
const hasLowerCase = /[a-z]/.test(password);
const hasNumbers = /\d/.test(password);
const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(password);

if (password.length < minLength) {
return { valid: false, message: '密码至少需要8个字符' };
}

if (!hasUpperCase || !hasLowerCase) {
return { valid: false, message: '密码必须包含大小写字母' };
}

if (!hasNumbers) {
return { valid: false, message: '密码必须包含数字' };
}

if (!hasSpecialChar) {
return { valid: false, message: '密码必须包含特殊字符' };
}

return { valid: true };
}

密码确认

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<input type="password" name="password" id="password" required>
<input type="password" name="password_confirm" id="password_confirm" required>

<script>
document.getElementById('password_confirm').addEventListener('input', function() {
const password = document.getElementById('password').value;
const confirm = this.value;

if (password !== confirm) {
this.setCustomValidity('密码不匹配');
} else {
this.setCustomValidity('');
}
});
</script>

3.4 文件上传安全

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<form enctype="multipart/form-data">
<!-- 限制文件类型和大小 -->
<input type="file"
name="upload"
accept=".jpg,.jpeg,.png,.gif"
required>

<!-- 最大文件大小提示 -->
<small>最大文件大小:5MB</small>

<button type="submit">上传</button>
</form>

<script>
document.querySelector('input[type="file"]').addEventListener('change', function(e) {
const file = e.target.files[0];

if (!file) return;

// 检查文件大小(5MB)
if (file.size > 5 * 1024 * 1024) {
alert('文件大小不能超过 5MB');
this.value = '';
return;
}

// 检查文件类型(客户端验证)
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
if (!allowedTypes.includes(file.type)) {
alert('只允许上传 JPG、PNG 或 GIF 图片');
this.value = '';
return;
}
});
</script>

服务器端验证(必需)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 示例:Node.js/Express
const multer = require('multer');
const path = require('path');

const upload = multer({
limits: { fileSize: 5 * 1024 * 1024 }, // 5MB
fileFilter: (req, file, cb) => {
const allowedTypes = /jpeg|jpg|png|gif/;
const extname = allowedTypes.test(path.extname(file.originalname).toLowerCase());
const mimetype = allowedTypes.test(file.mimetype);

if (mimetype && extname) {
return cb(null, true);
} else {
cb(new Error('只允许上传图片文件'));
}
}
});

3.5 表单提交安全

防止重复提交

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<form id="myForm">
<input type="text" name="data" required>
<button type="submit" id="submitBtn">提交</button>
</form>

<script>
let isSubmitting = false;

document.getElementById('myForm').addEventListener('submit', function(e) {
if (isSubmitting) {
e.preventDefault();
return false;
}

isSubmitting = true;
const submitBtn = document.getElementById('submitBtn');
submitBtn.disabled = true;
submitBtn.textContent = '提交中...';

// 如果使用 AJAX,在请求完成后重置
// fetch(...).finally(() => {
// isSubmitting = false;
// submitBtn.disabled = false;
// submitBtn.textContent = '提交';
// });
});
</script>

安全的表单提交

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 使用 FormData 和 fetch
const form = document.getElementById('myForm');

form.addEventListener('submit', async function(e) {
e.preventDefault();

const formData = new FormData(this);

// 添加 CSRF token
const csrfToken = document.querySelector('meta[name="csrf-token"]').content;

try {
const response = await fetch('/api/submit', {
method: 'POST',
headers: {
'X-CSRF-Token': csrfToken
},
body: formData
});

if (!response.ok) {
throw new Error('提交失败');
}

const result = await response.json();
alert('提交成功');
} catch (error) {
console.error('错误:', error);
alert('提交失败,请稍后重试');
}
});

4. 外链安全

外部链接可能指向恶意网站或导致安全问题,需要谨慎处理。

4.1 链接的 rel 属性

rel=”noopener”

防止新打开的页面访问 window.opener,防止潜在的安全风险。

1
2
3
4
5
<!-- ✅ 安全 -->
<a href="https://external-site.com" target="_blank" rel="noopener">外部链接</a>

<!-- ❌ 不安全 -->
<a href="https://external-site.com" target="_blank">外部链接</a>

rel=”noreferrer”

noopener 的基础上,还阻止发送 Referer 头。

1
<a href="https://external-site.com" target="_blank" rel="noreferrer">外部链接</a>

rel=”noopener noreferrer”(推荐)

同时使用两个属性,提供最好的安全保护。

1
2
3
<a href="https://external-site.com" target="_blank" rel="noopener noreferrer">
外部链接
</a>

为什么需要 noopener

恶意网站可能通过以下方式利用 window.opener

1
2
3
4
5
// 恶意网站中的代码
if (window.opener) {
// 重定向原始页面到钓鱼网站
window.opener.location = 'http://phishing-site.com';
}

4.2 验证外部链接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function isExternalLink(url) {
try {
const linkUrl = new URL(url, window.location.origin);
return linkUrl.origin !== window.location.origin;
} catch {
return false;
}
}

function createSafeLink(href, text) {
const link = document.createElement('a');
link.href = href;
link.textContent = text;

if (isExternalLink(href)) {
link.target = '_blank';
link.rel = 'noopener noreferrer';
}

return link;
}

// 自动为所有外部链接添加 rel="noopener noreferrer"
document.querySelectorAll('a[href^="http"]').forEach(link => {
if (isExternalLink(link.href)) {
if (!link.rel.includes('noopener')) {
link.rel = (link.rel ? link.rel + ' ' : '') + 'noopener noreferrer';
}
}
});

4.3 白名单验证

对于用户生成的内容中的链接,应该验证其安全性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const allowedDomains = [
'example.com',
'trusted-site.com',
'another-trusted.com'
];

function isAllowedDomain(url) {
try {
const urlObj = new URL(url);
return allowedDomains.includes(urlObj.hostname);
} catch {
return false;
}
}

function sanitizeLink(href) {
if (!isAllowedDomain(href)) {
// 重定向到警告页面,或使用代理
return '/warning?url=' + encodeURIComponent(href);
}
return href;
}

4.4 危险的 URL 协议

避免使用危险的 URL 协议。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const dangerousProtocols = ['javascript:', 'data:', 'vbscript:', 'file:'];

function isDangerousUrl(url) {
const protocol = url.split(':')[0].toLowerCase();
return dangerousProtocols.includes(protocol);
}

function createSafeLink(href, text) {
if (isDangerousUrl(href)) {
console.warn('危险的 URL:', href);
return null; // 不创建链接
}

const link = document.createElement('a');
link.href = href;
link.textContent = text;
return link;
}

4.5 链接安全检查清单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- ✅ 安全的内部链接 -->
<a href="/page.html">内部链接</a>
<a href="#section">锚点链接</a>

<!-- ✅ 安全的外部链接 -->
<a href="https://example.com"
target="_blank"
rel="noopener noreferrer"
title="在新窗口打开">
外部链接
</a>

<!-- ❌ 不安全的外部链接 -->
<a href="https://example.com" target="_blank">外部链接</a>

<!-- ❌ 危险的链接 -->
<a href="javascript:alert('XSS')">危险链接</a>
<a href="data:text/html,<script>alert('XSS')</script>">危险链接</a>

5. 其他安全最佳实践

5.1 HTTPS 使用

所有网站都应该使用 HTTPS。

1
2
<!-- 强制 HTTPS -->
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">

或者在服务器配置:

1
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

5.2 敏感信息处理

不要在 HTML 中暴露敏感信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- ❌ 不安全:在 HTML 中暴露敏感信息 -->
<div data-api-key="sk_live_abc123xyz">...</div>
<script>
const apiKey = 'sk_live_abc123xyz';
</script>

<!-- ✅ 安全:从安全的端点获取 -->
<script>
fetch('/api/get-config')
.then(res => res.json())
.then(config => {
// 使用配置
});
</script>

5.3 防止信息泄露

避免在错误信息中泄露敏感信息:

1
2
3
4
5
6
7
8
9
<!-- ❌ 不安全 -->
<div class="error">
数据库连接失败:用户 root,密码 123456
</div>

<!-- ✅ 安全 -->
<div class="error">
系统错误,请稍后重试
</div>

5.4 安全的 JSON-LD

在使用结构化数据时,注意安全:

1
2
3
4
5
6
7
8
9
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Person",
"name": "张三",
// ❌ 不要包含敏感信息如邮箱、电话等
// "email": "user@example.com"
}
</script>

5.5 安全测试工具

推荐使用以下工具检查网站安全性:

6. 完整的安全配置示例

6.1 HTML 页面安全配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">

<!-- 安全响应头(服务器端配置更推荐) -->
<meta http-equiv="Content-Security-Policy"
content="
default-src 'self';
script-src 'self' 'nonce-随机字符串';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
object-src 'none';
">
<meta http-equiv="X-Frame-Options" content="DENY">
<meta http-equiv="X-Content-Type-Options" content="nosniff">
<meta name="referrer" content="strict-origin-when-cross-origin">

<!-- CSRF Token -->
<meta name="csrf-token" content="服务器生成的token">

<title>安全示例页面</title>
</head>
<body>
<!-- 表单示例 -->
<form id="safeForm" method="POST">
<input type="hidden" name="csrf_token" id="csrf_token">

<input type="text" name="username" required minlength="3" maxlength="20">
<input type="email" name="email" required>
<input type="password" name="password" required minlength="8">

<button type="submit">提交</button>
</form>

<!-- 外部链接示例 -->
<a href="https://example.com"
target="_blank"
rel="noopener noreferrer">
安全的外部链接
</a>

<!-- 安全的脚本 -->
<script nonce="随机字符串">
// 初始化 CSRF token
document.getElementById('csrf_token').value =
document.querySelector('meta[name="csrf-token"]').content;

// 表单提交处理
document.getElementById('safeForm').addEventListener('submit', async function(e) {
e.preventDefault();

const formData = new FormData(this);
const csrfToken = document.querySelector('meta[name="csrf-token"]').content;

try {
const response = await fetch('/api/submit', {
method: 'POST',
headers: {
'X-CSRF-Token': csrfToken
},
body: formData
});

if (!response.ok) throw new Error('提交失败');

alert('提交成功');
} catch (error) {
console.error('错误:', error);
alert('提交失败');
}
});

// 自动为外部链接添加 rel="noopener noreferrer"
document.querySelectorAll('a[href^="http"]').forEach(link => {
if (new URL(link.href).origin !== window.location.origin) {
link.rel = (link.rel ? link.rel + ' ' : '') + 'noopener noreferrer';
}
});
</script>
</body>
</html>

6.2 服务器配置示例(Nginx)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
server {
listen 443 ssl http2;
server_name example.com;

# SSL 配置
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;

# 安全响应头
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'nonce-$request_id'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https://fonts.gstatic.com; connect-src 'self' https://api.example.com; frame-ancestors 'none'; object-src 'none'; base-uri 'self'; form-action 'self';" always;
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=(self)" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

location / {
root /var/www/html;
index index.html;
}
}

总结

本文档详细介绍了 HTML 开发中的安全最佳实践:

  1. 安全响应头:CSP、X-Frame-Options、HSTS 等
  2. XSS 防护:输入验证、输出编码、CSP 策略
  3. 表单安全:输入验证、CSRF 防护、密码安全、文件上传
  4. 外链安全:rel=”noopener noreferrer”、链接验证
  5. 其他实践:HTTPS、敏感信息处理、安全测试

关键要点

  • 永远不要信任用户输入:所有输入都要验证和过滤
  • 使用 HTTPS:保护数据传输安全
  • 实施 CSP:最有效的 XSS 防护措施
  • **外部链接使用 rel=”noopener noreferrer”**:防止恶意网站利用
  • 服务器端验证:客户端验证只是辅助,不能作为唯一安全措施
  • 保持更新:定期更新依赖和安全配置
  • 安全测试:使用工具定期检查网站安全性

安全是一个持续的过程,需要在前端、后端、服务器配置等多个层面协同工作。掌握这些最佳实践可以帮助你构建更安全的 Web 应用。