Skip to content

安全防护实践

环境信息:Node.js 20.x、helmet 7.x、@nestjs/throttler 5.x

CORS 跨域配置#

浏览器的同源策略会阻止跨域请求,需要在服务端配置 CORS:

main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule)
app.enableCors({
origin: ['http://localhost:5173', 'https://example.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
credentials: true,
})
await app.listen(3000)
}

也可以使用通配符允许所有域名(不推荐在生产环境使用):

app.enableCors({
origin: '*',
})

Helmet 安全头#

Helmet 通过设置 HTTP 响应头来保护应用免受常见的 Web 漏洞攻击:

Terminal window
pnpm add helmet -S
main.ts
import helmet from 'helmet'
async function bootstrap() {
const app = await NestFactory.create(AppModule)
app.use(helmet())
await app.listen(3000)
}

Helmet 默认开启以下安全策略:

可以根据需要自定义配置:

app.use(
helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"],
},
},
})
)

限流防护#

防止恶意用户频繁请求接口,可以使用 Throttler 模块:

Terminal window
pnpm add @nestjs/throttler -S

在 AppModule 中配置:

import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler'
import { APP_GUARD } from '@nestjs/core'
@Module({
imports: [
ThrottlerModule.forRoot([
{
ttl: 60000, // 时间窗口 60 秒
limit: 10, // 最多 10 次请求
},
]),
],
providers: [
{
provide: APP_GUARD,
useClass: ThrottlerGuard,
},
],
})
export class AppModule {}

对特定接口跳过限流:

import { SkipThrottle } from '@nestjs/throttler'
@SkipThrottle()
@Get('public')
getPublicData() {
return 'public data'
}

自定义接口限流规则:

import { Throttle } from '@nestjs/throttler'
@Throttle({ default: { limit: 3, ttl: 60000 } })
@Post('login')
login() {
// 登录接口每分钟最多 3 次
}

参数校验#

使用 ValidationPipe 配合 class-validator 校验请求参数:

Terminal window
pnpm add class-validator class-transformer -S

全局启用校验:

main.ts
import { ValidationPipe } from '@nestjs/common'
app.useGlobalPipes(
new ValidationPipe({
whitelist: true, // 过滤非 DTO 定义的属性
forbidNonWhitelisted: true, // 存在非白名单属性时报错
transform: true, // 自动转换类型
})
)

定义 DTO 校验规则:

import { IsString, IsEmail, MinLength, IsOptional } from 'class-validator'
export class CreateUserDto {
@IsString()
@MinLength(2)
name: string
@IsEmail()
email: string
@IsString()
@MinLength(6)
password: string
@IsOptional()
@IsString()
avatar?: string
}

SQL 注入防护#

使用 TypeORM 的参数化查询可以有效防止 SQL 注入:

// 安全的写法
const user = await this.userRepository.findOne({
where: { username },
})
// QueryBuilder 中使用参数
const users = await this.userRepository
.createQueryBuilder('user')
.where('user.name = :name', { name })
.getMany()

避免直接拼接 SQL 字符串:

// 危险!不要这样写
const users = await this.userRepository.query(
`SELECT * FROM user WHERE name = '${name}'`
)

XSS 防护#

对用户输入进行转义处理:

Terminal window
pnpm add sanitize-html -S
pnpm add @types/sanitize-html -D
import sanitizeHtml from 'sanitize-html'
@Injectable()
export class SanitizeService {
clean(dirty: string): string {
return sanitizeHtml(dirty, {
allowedTags: ['b', 'i', 'em', 'strong'],
allowedAttributes: {},
})
}
}

敏感信息保护#

不要在响应中暴露敏感信息:

user.entity.ts
import { Exclude } from 'class-transformer'
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number
@Column()
name: string
@Exclude()
@Column()
password: string
}
// 使用 ClassSerializerInterceptor
@UseInterceptors(ClassSerializerInterceptor)
@Get(':id')
findOne(@Param('id') id: number) {
return this.userService.findOne(id)
}

环境变量管理#

敏感配置不要硬编码在代码中:

.env
DB_PASSWORD = your_secret_password
JWT_SECRET = your_jwt_secret
// 使用 ConfigService 读取
@Injectable()
export class AuthService {
constructor(private configService: ConfigService) {}
getJwtSecret() {
return this.configService.get('JWT_SECRET')
}
}

确保 .env 文件已添加到 .gitignore 中,避免提交到版本库。