NestJS 공식문서 Rate Limiting

GGAE99·2023년 10월 16일
0

NestJS 공식 문서

목록 보기
29/33
post-thumbnail

Rate Limiting

애플리케이션을 무차별 대입 공격으로부터 보호하는 방법인 Rate-limiting(속도 제한)에 대한 설명입니다. 시작하려면 @nestjs/throttler 패키지를 설치해야 합니다.

$ npm i --save @nestjs/throttler

설치가 완료되면 ThrottlerModule을 다른 Nest 패키지와 마찬가지로 forRoot 또는 forRootAsync 메서드를 사용하여 구성할 수 있습니다.

// app.module.ts
import { ThrottlerModule } from '@nestjs/throttler';

@Module({
  imports: [
    ThrottlerModule.forRoot([{
      ttl: 60000,
      limit: 10,
    }]),
  ],
})
export class AppModule {}

위의 코드는 애플리케이션의 보호되는 경로에 대한 ttl(밀리초 단위의 지속 시간)과 limit(지정된 ttl 내에서의 최대 요청 수)과 같은 글로벌 옵션을 설정합니다.

모듈을 가져온 후 ThrottlerGuard를 어떻게 바인딩할지 선택할 수 있습니다. 가드 섹션에서 언급된 것처럼 어떤 종류의 바인딩이든 상관없습니다. 예를 들어 가드를 전역적으로 바인딩하려면 다음과 같이 어떤 모듈에서 이 프로바이더를 추가할 수 있습니다.

{
  provide: APP_GUARD,
  useClass: ThrottlerGuard
}

Multiple Throttler Definitions

여러 Throttler 정의를 설정하려는 경우도 있을 수 있습니다. 예를 들어 초당 3회, 10초 동안 20회, 1분 동안 100회 이상 호출을 허용하지 않는 정의를 설정하려면 다음과 같이 배열에 이름 있는 옵션과 함께 정의를 설정할 수 있습니다. 이러한 이름 있는 옵션은 나중에 @SkipThrottle()@Throttle() 데코레이터에서 다시 참조할 수 있습니다.

// app.module.ts
import { ThrottlerModule } from '@nestjs/throttler';

@Module({
  imports: [
    ThrottlerModule.forRoot([
      {
        name: 'short',
        ttl: 1000,
        limit: 3,
      },
      {
        name: 'medium',
        ttl: 10000,
        limit: 20
      },
      {
        name: 'long',
        ttl: 60000,
        limit: 100
      }
    ]),
  ],
})
export class AppModule {}

Customization

사용자 정의화도 가능합니다. 가드를 컨트롤러 또는 전역으로 바인딩하고자 하지만 하나 이상의 엔드포인트에서 rate limiting(속도 제한)을 비활성화하고자 할 때, @SkipThrottle() 데코레이터를 사용할 수 있습니다. 이 데코레이터는 클래스 전체 또는 단일 경로에 대해 rate limiting을 해제합니다. @SkipThrottle() 데코레이터는 또한 라우트를 건너뛰기 위한 문자열 키와 불리언 값으로 이루어진 객체를 허용하며, 대부분의 컨트롤러를 제외하고 각 라우트를 구성하려는 경우 해당 Throttler 세트마다 구성할 수 있습니다. 객체를 전달하지 않으면 기본값은 { default: true }입니다.

@SkipThrottle()
@Controller('users')
export class UsersController {}

@SkipThrottle() 데코레이터는 경로 또는 클래스를 건너뛰거나 건너뛰는 클래스 내에서 경로를 무시하는 데 사용할 수 있습니다.

@SkipThrottle()
@Controller('users')
export class UsersController {
  // Rate limiting is applied to this route.
  @SkipThrottle({ default: false })
  dontSkip() {
    return 'List users work with Rate limiting.';
  }
  // This route will skip rate limiting.
  doSkip() {
    return 'List users work without Rate limiting.';
  }
}

또한 @Throttle() 데코레이터를 사용하여 글로벌 모듈에서 설정한 limitttl을 무시하고 더 강화된 또는 느슨한 보안 옵션을 제공할 수 있습니다. 이 데코레이터는 클래스나 함수에도 사용할 수 있습니다. 버전 5 이상에서는 데코레이터가 Throttler 세트의 이름과 limit 및 ttl 키가 있는 정수 값을 가진 객체를 취하며, 이는 루트 모듈에 전달된 옵션과 유사합니다.
원래 옵션에 이름이 설정되어 있지 않은 경우, 문자열 default를 사용하십시오.

// Override default configuration for Rate limiting and duration.
@Throttle({ default: { limit: 3, ttl: 60000 } })
@Get()
findAll() {
  return "List users works with custom rate limiting.";
}

Proxies

만약 애플리케이션이 프록시 서버 뒤에서 실행된다면, 특정 HTTP 어댑터 옵션 (express 및 fastify)에 대한 trust proxy 옵션을 확인하고 활성화해야 합니다. 이렇게 하면 X-Forwarded-For 헤더에서 원본 IP 주소를 얻을 수 있으며, getTracker() 메서드를 req.ip 대신 헤더에서 값을 가져오도록 오버라이드할 수 있습니다. 다음 예제는 express 및 fastify 두 모두에서 작동합니다.

// throttler-behind-proxy.guard.ts
import { ThrottlerGuard } from '@nestjs/throttler';
import { Injectable } from '@nestjs/common';

@Injectable()
export class ThrottlerBehindProxyGuard extends ThrottlerGuard {
  protected async getTracker(req: Record<string, any>): Promise<string> {
    return req.ips.length ? req.ips[0] : req.ip; // individualize IP extraction to meet your own needs
  }
}

// app.controller.ts
import { ThrottlerBehindProxyGuard } from './throttler-behind-proxy.guard';

@UseGuards(ThrottlerBehindProxyGuard)

Websockets

이 모듈은 웹소켓과 함께 작동할 수 있지만, 일부 클래스 확장이 필요합니다. ThrottlerGuard를 확장하고 handleRequest 메서드를 다음과 같이 오버라이드할 수 있습니다.

@Injectable()
export class WsThrottlerGuard extends ThrottlerGuard {
  async handleRequest(context: ExecutionContext, limit: number, ttl: number): Promise<boolean> {
    const client = context.switchToWs().getClient();
    const ip = client._socket.remoteAddress;
    const key = this.generateKey(context, ip);
    const { totalHits } = await this.storageService.increment(key, ttl);

    if (totalHits > limit) {
      throw new ThrottlerException();
    }

    return true;
  }
}

웹소켓을 사용할 때 몇 가지 주의할 사항이 있습니다:

  • 가드를 APP_GUARD 또는 app.useGlobalGuards()와 등록할 수 없습니다.
  • 한계에 도달하면 Nest는 예외 이벤트를 발생시키므로 해당 이벤트를 수신할 수 있는 청취자가 준비되어 있는지 확인해야 합니다.

GraphQL

ThrottlerGuard는 GraphQL 요청과 함께 작동할 수도 있습니다. 다시 한 번 가드를 확장할 수 있지만 이번에는 getRequestResponse 메서드를 오버라이드합니다.

@Injectable()
export class GqlThrottlerGuard extends ThrottlerGuard {
  getRequestResponse(context: ExecutionContext) {
    const gqlCtx = GqlExecutionContext.create(context);
    const ctx = gqlCtx.getContext();
    return { req: ctx.req, res: ctx.res };
  }
}

Configuration

ThrottlerModule의 옵션 배열에 전달되는 객체에 대한 옵션입니다.

  • name: 사용 중인 쓰로틀러 세트를 내부 추적하기 위한 이름입니다. 전달되지 않으면 default로 기본값을 가집니다.
  • ttl: 각 요청이 저장되는 시간을 밀리초 단위로 나타냅니다.
  • limit: TTL 제한 내의 최대 요청 수입니다.
  • ignoreUserAgents: 쓰로틀링 요청에 대한 무시할 user-agent 정규 표현식의 배열입니다.
  • skipIf: ExecutionContext를 입력으로 받아 쓰로틀러 로직을 빠져 나가는 boolean 값을 반환하는 함수입니다. @SkipThrottler()와 유사하지만 요청을 기반으로 합니다.

Storages

내장 스토리지는 글로벌 옵션에서 설정한 TTL로 설정한 시간 이내의 요청을 추적하는 인메모리 캐시입니다. 클래스가 ThrottlerStorage 인터페이스를 구현하는 한, 사용자 정의 스토리지 옵션을 ThrottlerModulestorage 옵션에 넣을 수 있습니다. 분산 서버의 경우 Redis의 커뮤니티 스토리지 공급자를 사용하여 단일 진리의 소스를 갖을 수 있습니다.

Time Helpers

시간을 더 읽기 쉽게 만들기 위한 몇 가지 도우미 메서드가 있으며, 직접 정의보다 이러한 도우미를 사용하려는 경우에 사용할 수 있습니다. @nestjs/throttlerseconds, minutes, hours, daysweeks의 다섯 가지 다른 도우미를 내보냅니다. 이를 사용하려면 seconds(5) 또는 다른 도우미 중 하나를 호출하면 올바른 밀리초 수가 반환됩니다.

무차별 대입(Brute Force) 공격이란?

사용자의 인증 정보를 얻기 위해 공격자가 반복적으로 매번 다른 사용자 아이디와 비밀번호, 이름 등을 입력하는 방식의 공격이다. 보통 자동화된 툴이나 스크립트 또는 봇을 사용해 엑세스 권한을 얻을 때 까지 가능한 모든 조합을 대입한다.

무차별 대입 공격의 유형

  • 역 무차별 대입 공격 : 많은 계정을 대상으로 소수의 흔한 비밀번호를 반복 시도한다.
  • 인증 정보 스터핑 : 타사이트 또는 서비스에서 훔친 사용자 정보를 사용하여 다른 서비스에 계정을 하이재킹한다.
  • 사전 공격 : 사전의 얻은 정보를 통해 일반적인 비밀번호를 돌아가면서 시도한다.
  • 레인보우 테이블 공격 : 해싱함수를 반대로 되돌려 비밀번호를 알아낸다.

무차별 대입 공격 방어

  • 캡차 활성화
  • 다중 요소 인증 활성화
  • 로그인 시도 횟수 제한
  • 비밀번호 해시에 솔트를 넣음
  • 길고 복잡한 비밀번호 사용하도록 제한

0개의 댓글