CSP 与 HTTPS
CSP (Content Security Policy)
通过 HTTP 头告诉浏览器哪些资源是合法的,阻止注入攻击。
指令
http
Content-Security-Policy:
default-src 'self'; # 所有资源默认只允许本站
script-src 'self' https://cdn.com; # 脚本来源
style-src 'self' 'unsafe-inline'; # 样式来源(允许内联)
img-src 'self' data: https:; # 图片来源
connect-src 'self' https://api.com; # API 请求
font-src 'self' https://fonts.com; # 字体
frame-src 'none'; # 禁止嵌入 iframe
object-src 'none'; # 禁止插件
base-uri 'self'; # 限制 <base>
form-action 'self'; # 限制表单提交目标Content-Security-Policy:
default-src 'self'; # 所有资源默认只允许本站
script-src 'self' https://cdn.com; # 脚本来源
style-src 'self' 'unsafe-inline'; # 样式来源(允许内联)
img-src 'self' data: https:; # 图片来源
connect-src 'self' https://api.com; # API 请求
font-src 'self' https://fonts.com; # 字体
frame-src 'none'; # 禁止嵌入 iframe
object-src 'none'; # 禁止插件
base-uri 'self'; # 限制 <base>
form-action 'self'; # 限制表单提交目标常用值
| 值 | 含义 |
|---|---|
'self' | 同源 |
'none' | 禁止 |
'unsafe-inline' | 允许内联(尽量避克) |
'unsafe-eval' | 允许 eval(尽量避克) |
https: | 所有 HTTPS 来源 |
data: | data: URI |
'strict-dynamic' | 信任已加载脚本的动态加载 |
nonce-xxx | 只允许特定 nonce 的脚本 |
nonce 示例
js
// 服务端生成随机 nonce
const nonce = crypto.randomUUID()
// 设置 CSP
res.setHeader(
'Content-Security-Policy',
`script-src 'nonce-${nonce}'`
)
// HTML 中只有带正确 nonce 的脚本才能执行
<script nonce="<%= nonce %>">
console.log('This script runs')
</script>
<script>
console.log('This script blocked by CSP')
</script>// 服务端生成随机 nonce
const nonce = crypto.randomUUID()
// 设置 CSP
res.setHeader(
'Content-Security-Policy',
`script-src 'nonce-${nonce}'`
)
// HTML 中只有带正确 nonce 的脚本才能执行
<script nonce="<%= nonce %>">
console.log('This script runs')
</script>
<script>
console.log('This script blocked by CSP')
</script>报告模式
http
# 仅报告不阻止(上线前先观察)
Content-Security-Policy-Report-Only:
script-src 'self';
report-uri /csp-report
# 浏览器会 POST 违规报告
{
"csp-report": {
"document-uri": "https://example.com/",
"violated-directive": "script-src",
"blocked-uri": "https://evil.com/hack.js"
}
}# 仅报告不阻止(上线前先观察)
Content-Security-Policy-Report-Only:
script-src 'self';
report-uri /csp-report
# 浏览器会 POST 违规报告
{
"csp-report": {
"document-uri": "https://example.com/",
"violated-directive": "script-src",
"blocked-uri": "https://evil.com/hack.js"
}
}HTTPS (TLS/SSL)
TLS 握手过程
客户端 服务端
│ │
├─ ClientHello ──────────► │ 支持的加密套件、TLS 版本
│ │
│ ◄── ServerHello ────────┤ 选择的加密套件、证书
│ │
│ ◄── Certificate ────────┤ 服务端证书(含公钥)
│ │
│ ◄── ServerHelloDone ────┤
│ │
├─ ClientKeyExchange ────► │ 用公钥加密的预主密钥
│ │
├─ ChangeCipherSpec ─────► │ 开始加密通信
│ │
│ ◄── ChangeCipherSpec ───┤
│ │
└────── 加密通信 ──────────┘客户端 服务端
│ │
├─ ClientHello ──────────► │ 支持的加密套件、TLS 版本
│ │
│ ◄── ServerHello ────────┤ 选择的加密套件、证书
│ │
│ ◄── Certificate ────────┤ 服务端证书(含公钥)
│ │
│ ◄── ServerHelloDone ────┤
│ │
├─ ClientKeyExchange ────► │ 用公钥加密的预主密钥
│ │
├─ ChangeCipherSpec ─────► │ 开始加密通信
│ │
│ ◄── ChangeCipherSpec ───┤
│ │
└────── 加密通信 ──────────┘证书类型
| 类型 | 验证级别 | 价格 | 适用 |
|---|---|---|---|
| DV (Domain Validation) | 仅域名所有权 | 免费 | 个人博客 |
| OV (Organization) | 验证企业身份 | 中 | 企业网站 |
| EV (Extended) | 严格验证 | 高 | 银行/金融 |
免费证书方案
bash
# Let's Encrypt + Certbot
certbot --nginx -d example.com -d www.example.com
# acme.sh(更自动化)
curl https://get.acme.sh | sh
acme.sh --issue -d example.com --nginx
acme.sh --install-cert -d example.com --key-file /etc/nginx/ssl/key.pem --fullchain-file /etc/nginx/ssl/cert.pem# Let's Encrypt + Certbot
certbot --nginx -d example.com -d www.example.com
# acme.sh(更自动化)
curl https://get.acme.sh | sh
acme.sh --issue -d example.com --nginx
acme.sh --install-cert -d example.com --key-file /etc/nginx/ssl/key.pem --fullchain-file /etc/nginx/ssl/cert.pemHSTS(HTTP Strict Transport Security)
http
Strict-Transport-Security: max-age=31536000; includeSubDomains; preloadStrict-Transport-Security: max-age=31536000; includeSubDomains; preload- 告诉浏览器只通过 HTTPS 访问
includeSubDomains— 子域名也生效preload— 提交到浏览器预加载列表- 在 Nginx 中配置:
nginx
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;Nginx HTTPS 配置模板
nginx
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
add_header Strict-Transport-Security "max-age=31536000" always;
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options SAMEORIGIN;
add_header X-XSS-Protection "0";
}
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
add_header Strict-Transport-Security "max-age=31536000" always;
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options SAMEORIGIN;
add_header X-XSS-Protection "0";
}
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}安全相关 HTTP 头
nginx
# 在 Nginx 中添加安全头
add_header X-Content-Type-Options nosniff; # 禁止 MIME 嗅探
add_header X-Frame-Options SAMEORIGIN; # 防止点击劫持
add_header X-XSS-Protection "0"; # 已废弃但兼容
add_header Referrer-Policy strict-origin-when-cross-origin; # 引用策略
add_header Permissions-Policy "camera=(), microphone=()"; # 权限限制# 在 Nginx 中添加安全头
add_header X-Content-Type-Options nosniff; # 禁止 MIME 嗅探
add_header X-Frame-Options SAMEORIGIN; # 防止点击劫持
add_header X-XSS-Protection "0"; # 已废弃但兼容
add_header Referrer-Policy strict-origin-when-cross-origin; # 引用策略
add_header Permissions-Policy "camera=(), microphone=()"; # 权限限制
Cao Note