安全最佳实践 #
一、安全概述 #
1.1 常见安全威胁 #
| 威胁 | 说明 |
|---|---|
| XSS | 跨站脚本攻击 |
| CSRF | 跨站请求伪造 |
| SQL注入 | 数据库注入攻击 |
| 点击劫持 | iframe嵌入攻击 |
| 中间人攻击 | 数据传输劫持 |
1.2 Hapi安全特性 #
- 内置输入验证
- 安全的Cookie处理
- CORS配置
- 认证授权系统
二、输入验证 #
2.1 Joi验证 #
javascript
const Joi = require('@hapi/joi');
server.route({
method: 'POST',
path: '/users',
options: {
validate: {
payload: Joi.object({
name: Joi.string().min(2).max(50).required(),
email: Joi.string().email().required(),
password: Joi.string().min(8).max(50).required()
.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/)
})
}
},
handler: createUser
});
2.2 输入清理 #
javascript
server.ext('onPreHandler', (request, h) => {
if (request.payload) {
request.payload = sanitizeInput(request.payload);
}
return h.continue;
});
function sanitizeInput(data) {
if (typeof data === 'string') {
return data.replace(/<[^>]*>/g, '');
}
if (typeof data === 'object') {
Object.keys(data).forEach(key => {
data[key] = sanitizeInput(data[key]);
});
}
return data;
}
2.3 SQL注入防护 #
javascript
server.route({
method: 'GET',
path: '/users',
handler: async (request, h) => {
const { name } = request.query;
const query = 'SELECT * FROM users WHERE name = $1';
const result = await db.query(query, [name]);
return result.rows;
}
});
三、安全响应头 #
3.1 全局安全头 #
javascript
server.ext('onPreResponse', (request, h) => {
const response = request.response;
if (!response.isBoom) {
response
.header('X-Content-Type-Options', 'nosniff')
.header('X-Frame-Options', 'DENY')
.header('X-XSS-Protection', '1; mode=block')
.header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains')
.header('Content-Security-Policy', "default-src 'self'");
}
return h.continue;
});
3.2 安全头说明 #
| 头 | 说明 |
|---|---|
| X-Content-Type-Options | 防止MIME类型嗅探 |
| X-Frame-Options | 防止点击劫持 |
| X-XSS-Protection | XSS过滤器 |
| Strict-Transport-Security | 强制HTTPS |
| Content-Security-Policy | 内容安全策略 |
3.3 Content-Security-Policy #
javascript
server.ext('onPreResponse', (request, h) => {
const response = request.response;
if (!response.isBoom) {
response.header('Content-Security-Policy', [
"default-src 'self'",
"script-src 'self' 'unsafe-inline'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data:",
"font-src 'self'",
"connect-src 'self'",
"frame-ancestors 'none'"
].join('; '));
}
return h.continue;
});
四、Cookie安全 #
4.1 安全Cookie配置 #
javascript
server.state('sessionId', {
ttl: 24 * 60 * 60 * 1000,
isSecure: true,
isHttpOnly: true,
path: '/',
sameSite: 'Strict'
});
4.2 Cookie选项说明 #
| 选项 | 说明 |
|---|---|
| isSecure | 仅HTTPS传输 |
| isHttpOnly | 禁止JavaScript访问 |
| sameSite | 同站策略 |
| ttl | 有效期 |
4.3 SameSite配置 #
javascript
server.state('sessionId', {
sameSite: 'Strict'
});
server.state('analytics', {
sameSite: 'Lax'
});
五、CSRF防护 #
5.1 安装Crumb #
bash
npm install @hapi/crumb
5.2 配置CSRF #
javascript
const Crumb = require('@hapi/crumb');
await server.register({
plugin: Crumb,
options: {
cookieOptions: {
isSecure: true
},
restful: true
}
});
5.3 使用CSRF #
javascript
server.route({
method: 'POST',
path: '/form',
options: {
plugins: {
crumb: true
}
},
handler: (request, h) => {
return { message: 'Form submitted' };
}
});
六、认证安全 #
6.1 密码安全 #
javascript
const bcrypt = require('bcrypt');
const saltRounds = 12;
server.route({
method: 'POST',
path: '/register',
handler: async (request, h) => {
const { password } = request.payload;
const hashedPassword = await bcrypt.hash(password, saltRounds);
const user = await User.create({
...request.payload,
password: hashedPassword
});
return h.response(user).code(201);
}
});
6.2 登录限流 #
javascript
const loginAttempts = new Map();
server.route({
method: 'POST',
path: '/login',
handler: async (request, h) => {
const { username } = request.payload;
const ip = request.info.remoteAddress;
const key = `${ip}:${username}`;
const attempts = loginAttempts.get(key) || 0;
if (attempts >= 5) {
throw Boom.tooManyRequests('Too many login attempts');
}
const user = await authenticate(request.payload);
if (!user) {
loginAttempts.set(key, attempts + 1);
setTimeout(() => loginAttempts.delete(key), 15 * 60 * 1000);
throw Boom.unauthorized('Invalid credentials');
}
loginAttempts.delete(key);
return { token: generateToken(user) };
}
});
6.3 JWT安全 #
javascript
server.auth.strategy('jwt', 'jwt', {
keys: process.env.JWT_SECRET,
verify: {
aud: 'urn:audience:myapp',
iss: 'urn:issuer:myapp',
sub: false,
nbf: true,
exp: true,
maxAgeSec: 3600
},
validate: (artifacts, request, h) => {
return {
isValid: true,
credentials: artifacts.decoded.payload
};
}
});
七、CORS配置 #
7.1 全局CORS #
javascript
const server = Hapi.server({
port: 3000,
routes: {
cors: {
origin: ['https://example.com'],
headers: ['Accept', 'Content-Type', 'Authorization'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
credentials: true
}
}
});
7.2 路由级CORS #
javascript
server.route({
method: 'GET',
path: '/api/data',
options: {
cors: {
origin: ['https://trusted-domain.com'],
headers: ['Accept', 'Content-Type'],
additionalHeaders: ['X-Custom-Header']
}
},
handler: (request, h) => {
return { data: 'value' };
}
});
八、HTTPS配置 #
8.1 启用HTTPS #
javascript
const fs = require('fs');
const tls = {
key: fs.readFileSync('server.key'),
cert: fs.readFileSync('server.crt')
};
const server = Hapi.server({
port: 443,
tls: tls
});
8.2 HTTP重定向 #
javascript
const http = require('http');
http.createServer((req, res) => {
res.writeHead(301, {
Location: `https://${req.headers.host}${req.url}`
});
res.end();
}).listen(80);
九、日志安全 #
9.1 敏感信息过滤 #
javascript
server.ext('onPreResponse', (request, h) => {
const response = request.response;
if (response.isBoom) {
console.error({
timestamp: new Date().toISOString(),
path: request.path,
method: request.method,
error: response.message,
stack: process.env.NODE_ENV === 'development' ? response.stack : undefined
});
}
return h.continue;
});
9.2 请求日志 #
javascript
server.events.on('response', (request) => {
const log = {
timestamp: new Date().toISOString(),
method: request.method,
path: request.path,
status: request.response.statusCode,
ip: request.info.remoteAddress,
userAgent: request.headers['user-agent']
};
console.log(JSON.stringify(log));
});
十、安全检查清单 #
10.1 检查项 #
- [ ] 输入验证
- [ ] 输出编码
- [ ] 安全响应头
- [ ] Cookie安全配置
- [ ] CSRF防护
- [ ] 认证安全
- [ ] HTTPS启用
- [ ] CORS配置
- [ ] 错误处理
- [ ] 日志安全
10.2 安全配置示例 #
javascript
const Hapi = require('@hapi/hapi');
const Crumb = require('@hapi/crumb');
const Joi = require('@hapi/joi');
const init = async () => {
const server = Hapi.server({
port: 3000,
routes: {
cors: {
origin: ['https://example.com'],
credentials: true
},
security: {
hsts: { maxAge: 31536000, includeSubDomains: true },
xframe: 'deny',
xss: true,
noSniff: true
}
}
});
await server.register({
plugin: Crumb,
options: {
cookieOptions: {
isSecure: true,
isHttpOnly: true
}
}
});
server.state('sessionId', {
ttl: 24 * 60 * 60 * 1000,
isSecure: true,
isHttpOnly: true,
sameSite: 'Strict'
});
server.ext('onPreResponse', (request, h) => {
const response = request.response;
if (!response.isBoom) {
response
.header('X-Content-Type-Options', 'nosniff')
.header('X-Frame-Options', 'DENY')
.header('X-XSS-Protection', '1; mode=block');
}
return h.continue;
});
await server.start();
console.log('Server running on %s', server.info.uri);
};
init();
十一、总结 #
安全最佳实践要点:
| 安全措施 | 说明 |
|---|---|
| 输入验证 | Joi验证 |
| 安全响应头 | X-Frame-Options等 |
| Cookie安全 | Secure, HttpOnly |
| CSRF防护 | @hapi/crumb |
| 认证安全 | 密码加密、限流 |
| HTTPS | TLS配置 |
下一步,让我们学习测试!
最后更新:2026-03-28