打包之前安全配置:CORS&helmet&RateLimit
CORS ------ 跨域资源共享
浏览器出于同源策略 ,默认禁止前端页面向「协议/域名/端口」任一不同的后端发请求。CORS 就是后端通过响应头告诉浏览器:「这个源(TcOrigin)我是允许访问的」。
常见错误配置(务必避免)
| 错误写法 | 风险 |
|---|---|
app.enableCors() 无参数 |
默认只允许同源,等价于没配,前端一调就跨域报错 |
origin: '*' + credentials: true |
浏览器直接拒绝 ,且 * 等于对任何网站敞开 |
生产环境还用 origin: true(反射请求源) |
任意网站都能带凭证访问你的接口 |
正确配置(白名单 + 按需开放)
TypeScript
const whitelist = ['https://app.example.com', 'https://admin.example.com'];
const corsOptions: CorsOptions = {
origin: (origin, callback) => {
// 允许白名单内,或同源/无 origin(如 Postman、服务端调用)
if (!origin || whitelist.includes(origin)) {
callback(null, true);
} else {
callback(new Error('CORS 不允许该来源'));
}
},
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
allowedHeaders: ['Content-Type', 'Authorization'],
exposedHeaders: ['X-Total-Count'], // 让前端可读的自定义响应头
credentials: true, // 允许携带 Cookie / Authorization
maxAge: 600, // 预检请求结果缓存 600 秒
preflightContinue: false, // 直接处理 OPTIONS,不往下穿透
};
app.enableCors(corsOptions);
关键点 :credentials: true 时 origin 必须是具体域名 ,不能再用 *。如果是公开 API 不需要 Cookie,则可以 origin: '*' 但务必把 credentials 关掉。
Helmet ------ 安全响应头中间件
Helmet 是 Express 系的安全中间件,它帮你自动设置一组安全相关的 HTTP 响应头 ,堵住浏览器层面的各类攻击面(点击劫持、MIME 嗅探、XSS 探针、降级到 HTTP 等)。NestJS 基于 Express(默认)因此直接
app.use(helmet())即可。
| 响应头 | 作用 |
|---|---|
Content-Security-Policy |
最核心:限制页面能加载哪些脚本/资源,防 XSS |
X-Frame-Options: DENY |
禁止被 iframe 嵌套,防点击劫持 |
Strict-Transport-Security |
强制后续只用 HTTPS,防降级攻击 |
X-Content-Type-Options: nosniff |
禁止浏览器猜测 MIME 类型 |
Referrer-Policy |
控制 Referer 泄露程度 |
X-DNS-Prefetch-Control |
关闭 DNS 预取 |
基础用法
TypeScript
import helmet from 'helmet';
app.use(helmet());
生产环境精细化(常需针对 CSP 调整)
TypeScript
app.use(
helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"], // 注意:内联脚本有 XSS 风险
imgSrc: ["'self'", 'data:', 'https://cdn.example.com'],
styleSrc: ["'self'", "'unsafe-inline'"],
},
},
// 关闭已不必要的(如纯 API 服务不需要 frameguard 可保留,但跨域策略需明确)
crossOriginEmbedderPolicy: false,
}),
);
踩坑提醒 :开启 Content-Security-Policy 后,前端若用了内联脚本、外部 CDN、eval 等,页面会直接挂掉。纯后端 API(无页面)影响不大;若前后端同域带页面,务必和前端联调 CSP 规则。
RateLimit ------ 接口限流
限制单个客户端在给定时间窗口内的请求次数,防御暴力破解、短信轰炸、CC/DoS、爬虫刷接口。
推荐方案 :@nestjs/throttler
TypeScript
pnpm add @nestjs/throttler
1) 模块注册(在 AppModule)
TypeScript
import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler';
import { APP_GUARD } from '@nestjs/core';
@Module({
imports: [
ThrottlerModule.forRoot({
throttlers: [
{ ttl: 60000, limit: 100 }, // 默认:每 60 秒最多 100 次
],
// storage: 默认内存存储,集群/多实例部署要换 RedisStorage
}),
],
providers: [
{ provide: APP_GUARD, useClass: ThrottlerGuard }, // 全局生效
],
})
export class AppModule {}
2) 局部覆盖(针对登录等敏感接口更严格)
TypeScript
@Post('signin')
@Throttle({ default: { limit: 5, ttl: 60000 } }) // 登录:每分钟仅 5 次
async signin(@Body() dto: SigninDto) { /* ... */ }
3) 自定义被限流时的响应(默认抛 429,可改文案)
TypeScript
import { ThrottlerGuard } from '@nestjs/throttler';
@Injectable()
export class CustomThrottlerGuard extends ThrottlerGuard {
protected async throwThrottlingException(): Promise<void> {
throw new HttpException('请求过于频繁,请稍后再试', HttpStatus.TOO_MANY_REQUESTS);
}
}
部署注意 :@nestjs/throttler 默认用进程内存 计数。如果你用 PM2 多进程、或 Kubernetes 多副本,每个实例各算各的,限流会失效------此时必须换成 RedisStorage(或 clusterMemory / 外部存储),让计数共享。
一份可直接用的整合 main.ts
TypeScript
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import helmet from 'helmet';
import { AppModule } from './app.module';
import { CorsOptions } from 'cors';
const whitelist = ['https://app.example.com'];
async function bootstrap() {
const app = await NestFactory.create(AppModule, { cors: false });
// 1. 安全头
app.use(helmet());
// 2. CORS(白名单 + 支持凭证)
app.enableCors({
origin: (origin, cb) => (!origin || whitelist.includes(origin))
? cb(null, true) : cb(new Error('CORS 不允许该来源')),
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
});
// 3. 全局校验(配合 DTO,防脏数据注入)
app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));
await app.listen(3000);
}
bootstrap();