Nest.js 클릭 연타 방지

SEUNGJUN·2024년 6월 6일
post-thumbnail

클릭 연타로 인해 API가 여러 번 호출되는 문제는 웹 애플리케이션에서 자주 발생하는 문제라고 볼수 있다. 이를 방지하고 백엔드에서 안정적이고 보안적으로 처리하기 위해 다양한 접근 방법이 있다.

1. Rate Limiting

Rate limiting은 일정 시간 내에 허용된 요청 수를 제한하는 방법이다. 이는 일반적으로 DDOS 공격 방지에도 사용된다. NestJS에서 rate limiting을 구현하려면 nestjs-throttler와 같은 패키지를 사용할 수 있다.

// 설치
npm install --save @nestjs/throttler
// app.module.ts
import { Module } from '@nestjs/common';
import { ThrottlerModule } from '@nestjs/throttler';

@Module({
  imports: [
    ThrottlerModule.forRoot({
      ttl: 60, // 시간 간격(초)
      limit: 10, // 허용된 최대 요청 수
    }),
  ],
})
export class AppModule {}
// any.controller.ts
import { Controller, Get, UseGuards } from '@nestjs/common';
import { ThrottlerGuard } from '@nestjs/throttler';

@UseGuards(ThrottlerGuard)
@Controller('any')
export class AnyController {
  @Get()
  findAll() {
    return 'This action returns all resources';
  }
}

2. Idempotency Key

Idempotency Key는 클라이언트가 요청 시 고유 키를 보내고, 서버가 이 키를 사용하여 동일한 요청을 한 번만 처리하도록 하는 방법이다. 이는 특히 결제 API나 데이터베이스에 영향을 주는 요청에 유용하다.

// idempotency.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class IdempotencyMiddleware implements NestMiddleware {
  private requests = new Set<string>();

  use(req: Request, res: Response, next: NextFunction) {
    const idempotencyKey = req.headers['idempotency-key'];

    if (idempotencyKey && this.requests.has(idempotencyKey)) {
      return res.status(409).send('Duplicate request');
    }

    if (idempotencyKey) {
      this.requests.add(idempotencyKey);
    }

    res.on('finish', () => {
      if (idempotencyKey) {
        this.requests.delete(idempotencyKey);
      }
    });

    next();
  }
}
// app.module.ts
import { Module, MiddlewareConsumer } from '@nestjs/common';
import { IdempotencyMiddleware } from './idempotency.middleware';

@Module({
  // imports, controllers, providers
})
export class AppModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(IdempotencyMiddleware)
      .forRoutes('*'); // 특정 경로에만 적용할 수도 있다.
  }
}

3. Optimistic Locking

Optimistic Locking은 동일한 리소스에 대한 동시성 제어를 위해 사용된다. 이는 주로 데이터베이스 트랜잭션에서 사용되며, 동일한 리소스에 대해 여러 번 업데이트가 발생하는 것을 방지한다.

// entity.ts
import {
  Entity,
  PrimaryGeneratedColumn,
  VersionColumn,
  Column,
} from 'typeorm';

@Entity()
export class SomeEntity {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  data: string;

  @VersionColumn()
  version: number;
}
// some.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { SomeEntity } from './entity';

@Injectable()
export class SomeService {
  constructor(
    @InjectRepository(SomeEntity)
    private readonly someRepository: Repository<SomeEntity>,
  ) {}

  async updateEntity(id: number, newData: string) {
    const entity = await this.someRepository.findOneByOrFail({ id });
    entity.data = newData;
    return this.someRepository.save(entity);
  }
}

4. Request Deduplication

요청이 처리되는 동안 동일한 요청이 반복되지 않도록 하는 방법이다. 이는 Redis와 같은 인메모리 데이터 스토어를 사용하여 구현할 수 있다.

// deduplication.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import * as redis from 'redis';

@Injectable()
export class DeduplicationMiddleware implements NestMiddleware {
  private redisClient = redis.createClient();

  use(req: Request, res: Response, next: NextFunction) {
    const deduplicationKey = `request-${req.method}-${req.originalUrl}`;

    this.redisClient.get(deduplicationKey, (err, reply) => {
      if (reply) {
        return res.status(409).send('Duplicate request in progress');
      }

      this.redisClient.set(deduplicationKey, 'true', 'EX', 60);
      
      res.on('finish', () => {
        this.redisClient.del(deduplicationKey);
      });

      next();
    });
  }
}
// app.module.ts
import { Module, MiddlewareConsumer } from '@nestjs/common';
import { DeduplicationMiddleware } from './deduplication.middleware';

@Module({
  // imports, controllers, providers
})
export class AppModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(DeduplicationMiddleware)
      .forRoutes('*');
  }
}

이러한 방법들을 통해 백엔드에서 클릭 연타로 인한 API 여러 번 호출 문제를 효과적으로 처리할 수 있다. 각 방법의 장단점을 고려하여 상황에 맞게 적절한 방법을 선택하는 것이 중요하다. NestJS는 이러한 문제를 해결하기 위한 다양한 기능을 제공하므로, 이를 활용하여 보다 안정적이고 보안적인 애플리케이션을 개발할 수 있다.

profile
RECORD DEVELOPER

0개의 댓글