Chuyển đến nội dung chính

レッスン 11: セキュリティのベスト プラクティス

ヘルメット、CORS 構成、@nestjs/throttler によるレート制限、CSRF 保護、入力サニタイズ。セキュリティヘッダー、HTTPS、@nestjs/config による環境変数。

💻 プログラミング — レッスン 11 レッスン 11: セキュリティのベスト プラクティス

NestJS: 基本から高度まで

パート 3: 認証とセキュリティ

xdev.asia

1. ヘルメット — セキュリティヘッダー

npm install helmet
// main.ts
import helmet from 'helmet';

const app = await NestFactory.create(AppModule);
app.use(helmet());
// Tự động thêm headers:
// X-Content-Type-Options: nosniff
// X-Frame-Options: SAMEORIGIN
// X-XSS-Protection: 0
// Strict-Transport-Security: max-age=15552000
// Content-Security-Policy: default-src 'self'

2. CORS の構成

const app = await NestFactory.create(AppModule);
app.enableCors({
  origin: [
    'https://myapp.com',
    'https://admin.myapp.com',
  ],
  methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true,
  maxAge: 3600,
});

3. @nestjs/throttler によるレート制限

npm install @nestjs/throttler
// app.module.ts
import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler';

@Module({
  imports: [
    ThrottlerModule.forRoot([
      { name: 'short', ttl: 1000, limit: 3 },   // 3 req/giây
      { name: 'medium', ttl: 10000, limit: 20 }, // 20 req/10 giây
      { name: 'long', ttl: 60000, limit: 100 },  // 100 req/phút
    ]),
  ],
  providers: [
    { provide: APP_GUARD, useClass: ThrottlerGuard }, // Global
  ],
})
export class AppModule {}

// Custom limits per route
@Throttle([{ name: 'short', ttl: 1000, limit: 1 }])  // 1 req/giây
@Post('login')
login() { ... }

// Skip throttling
@SkipThrottle()
@Get('health')
health() { return 'OK'; }

4. @nestjs/config による環境変数

npm install @nestjs/config joi
// app.module.ts
import { ConfigModule, ConfigService } from '@nestjs/config';
import * as Joi from 'joi';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath: `.env.${process.env.NODE_ENV || 'development'}`,
      validationSchema: Joi.object({
        NODE_ENV: Joi.string().valid('development', 'production', 'test').default('development'),
        PORT: Joi.number().default(3000),
        DATABASE_URL: Joi.string().required(),
        JWT_ACCESS_SECRET: Joi.string().required().min(32),
        JWT_REFRESH_SECRET: Joi.string().required().min(32),
        REDIS_URL: Joi.string().optional(),
      }),
    }),
  ],
})
export class AppModule {}

// Sử dụng
@Injectable()
export class AuthService {
  constructor(private config: ConfigService) {}

  getJwtSecret(): string {
    return this.config.get<string>('JWT_ACCESS_SECRET');
  }
}

5. 入力のサニタイズ

// Dùng class-transformer để sanitize
import { Transform } from 'class-transformer';

export class CreateCommentDto {
  @IsString()
  @Transform(({ value }) => value.replace(/<[^>]*>/g, ''))  // Strip HTML tags
  content: string;

  @IsString()
  @Transform(({ value }) => value.trim().toLowerCase())
  email: string;
}

// Hoặc dùng DOMPurify/sanitize-html cho HTML content
import sanitizeHtml from 'sanitize-html';

@Transform(({ value }) => sanitizeHtml(value, {
  allowedTags: ['b', 'i', 'em', 'strong', 'a', 'p', 'br'],
  allowedAttributes: { a: ['href'] },
}))
htmlContent: string;

6. SQL インジェクションの防止

// ✅ Đúng — Parameterized queries (TypeORM)
const users = await repo.createQueryBuilder('user')
  .where('user.email = :email', { email: userInput })
  .getMany();

// ✅ Đúng — Prisma (tự động parameterized)
const users = await prisma.user.findMany({
  where: { email: userInput },
});

// ❌ SAI — String concatenation
const users = await repo.query(`SELECT * FROM users WHERE email = '${userInput}'`);

7. その他のベストプラクティス

// 1. Compression
import compression from 'compression';
app.use(compression());

// 2. Request size limit
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ limit: '10mb', extended: true }));

// 3. Logging — không log sensitive data
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler) {
    const req = context.switchToHttp().getRequest();
    const { password, token, ...safeBody } = req.body;
    console.log(`${req.method} ${req.url}`, safeBody);
    return next.handle();
  }
}

// 4. Graceful shutdown
app.enableShutdownHooks();

8. まとめ

脅威解決策
XSSヘルメット、入力サニタイズ、CSP ヘッダー
SQLインジェクションパラメータ化されたクエリ (TypeORM/Prisma)
ブルートフォースレート制限 (@nestjs/throttler)
CSRFSameSite Cookie、CSRF トークン
機密データ@nestjs/config、.env ファイル、パスワードをログに記録しない
クリックジャッキングヘルメット経由の X フレーム オプション
コルスホワイトリストの発信元

次の記事で詳しく説明します セッション、Cookie、OAuth2。