개발
Laravel 6.x

라라벨 스로틀 미들웨어로 접근 제한 걸기

39 views as of November 22, 2024.
우리가 웹 개발을 하다보면 특정 요청에 대해서 제한적으로 처리해야할 경우가 있다.

예를 들어서 디비에 무언가를 저장하는 행위라던지, 가져오는 행위등이 너무 과하게 요청되거나 짧은 시간에 반복요청되면 안되듯이 말이다.

보통 대부분의 경우엔 앞단에서 뒷단으로 넘어가는 페이지 요청에 의해 자연적으로 딜레이가 발생하며 특정 요청이 과다하게 발생하는게 막아지기도 하며, 입력하고 넘기는 과정에서 검증이라는 절차때문에도 과하게 요청되는 경우는 사실상 없다고 볼 수있다.


사람과 봇의 페이지 요청

물론 사용하는사람이 '사람'에 한해서 말이다.


무슨소리냐면 인터넷은 사람이 엑세스하는거 이상으로 봇이 엑세스하는 요청도 엄청 많다.
또 이게 순수한 봇의 접근이면 크롤링의 목적으로 이해할 수 도 있지만 악의적인 목적으로 봇처럼 보이게하는 무차별 페이지 요청도 발생한다.

이런 경우에 대비해 우리는 인간이 직접 하는거 이상의 요청이 들어오면 능동적으로 막아낼 수 있는 작업을 쳐야하는데 라라벨에서는 이런 기능을 스로틀링이라는 기법으로 미들웨어에서 기본적으로 지원한다.



라라벨 스로틀링

라라벨의 ThrottleRequests 미들웨어는 API와 웹 앱의 요청 빈도를 제한하는데 사용된다.
쉽게말해 시스템의 부하방지를 위해 쓰며, 악의적인 공격을 차단하는데 매우 유효하다.

스로틀링은 지정된 단위(분)을 기준으로 행위수(Request)를 지정해 미들웨어 단에서 차단할 수 있으며 이러한 기준은 클라이언트의 IP 주소를 기반으로 작동한다.

주요 기능으로써는 아래와 같은 것이 있다.


요청 제한

분단위의 특정기간을 지정해 허용할 최대 요청 횟수를 지정할 수 있다.
지정한 요청보다 더 많은 요청이 온다면 HTTP 429(Too Many Requests)의 서버응답이 반환된다.


미들웨어 동작

스로틀 기능을 지원하는 ThrottleRequests 는 라라벨의 기본 미들웨어로 이미 선언, 초기화 되어있으며 이를 라우트에서 쉽게 이용 할 수 있다.


동적 설정 가능

기본 설정이 1분이내 10회 이상으로 설정되어있는 값을 언제든지 파라미터 전달 방식으로 쉽게 제어해서 사용할 수 있다.


코드 예제

스로틀은 라라벨 라우터(route/web.php)에서 아래와 같은 방법으로 제어 할 수 있다.

라우트 단독 적용 (2분내 5번의 요청까지 허용)
Route::get('/example', 'ExampleController@index')->middleware('throttle:5,2');Copy

라우트 그룹 적용 (1분내 10번의 요청까지 허용)
Route::middleware(['throttle:10,1'])->group(function () {
    Route::get('/example', 'ExampleController@index');
});Copy


HTTP 응답

만약에 스로틀링이 걸린다면 미들웨어는 아래와 같은 JSON 형식의 리턴을 클라이언트에게 제공한다.

{
    "message": "Too Many Requests."
}Copy

또 응답 헤더에 아래와 같은 정보도 포함해서 반환한다.

- X-Ratelimit-Limit: 허용된 최대 요청 회수.
- X-Ratelimit-Remaining: 남은 요청 회수.
- Retry-After: 제한 해제까지 남은 시간(초 단위).



내맘대로 스로틀 개조 및 리턴 응답 변경

하지만 실제로 사용하다보면 기본 미들웨어만으로는 원하는대로 적재적소에 쓰기가 힘들다.

쉬운 예제를 들자면 위 미들웨어는 회원/비회원의 차이로 다른 쓰로틀 환경을 제공하는것이 불가능하다.

나같은 경우는 회원인 경우는 무제한, 비회원인 경우는 제한 댓글쓰기 기능을 위해서 아래와 같이 새로운 스로틀 미들웨어를 구성해보았다.


미들웨어 생성 및 작성

우선 아티즌 명령어로 쉽게 미들웨어를 만들어보자

php artisan make:middleware ThrottleForGuestsCopy


그러면 app/Http/Middleware/ThrottleForGuests.php 위치에 새로운 미들웨어가 생성된다.
그럼 해당 파일을 열어 아래처럼 필요한 기능의 미들웨어로 개선을 해주자.

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Routing\Middleware\ThrottleRequests;
use Illuminate\Support\Facades\Auth;

class ThrottleForGuests
{
    public function handle($request, Closure $next, $maxAttempts = 2, $decayMinutes = 1)
    {
        // 비회원에게만 쓰로틀링 적용
        if (!Auth::check()) {
            // Laravel 기본 ThrottleRequests 미들웨어 호출
            return app(ThrottleRequests::class)->handle($request, $next, $maxAttempts, $decayMinutes);
        }

        // 회원 요청은 그대로 통과
        return $next($request);
    }
}
Copy


이제 나같은 경우는 라라벨 6.x 버전을 사용하기 때문에 살짝 최근코드랑 다르다.

아마 라라벨 8버전 이후부터는 아래코드처럼 적어야할 것이다.

namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\Auth;

class ThrottleForGuests
{
    public function handle($request, Closure $next)
    {
        // 비회원에게만 쓰로틀링 적용
        if (!Auth::check()) {
            $request->setRouteResolver(function () use ($request) {
                $request->route()->middleware('throttle:2,1'); // 1분에 2번 제한
                return $request->route();
            });
        }

        return $next($request);
    }
}Copy


구버전에는 setRouteResolver가 미지원이라...

어찌되었든 이렇게 미들웨어를 작성하고나면 이제 커널파일(app/Http/Kernel.php)에 라우트 미들웨어에 내가 작성한 미들웨어를 등록해주면된다.

protected $routeMiddleware = [
    // 기타 미들웨어...
    'throttleForGuests' => \App\Http\Middleware\ThrottleForGuests::class,
];Copy


이러면 이제 아래와 같이 내가 필요한 라우트에서 비회원 전용 미들웨어를 아래와 같이 선언해서 사용할 수 있다.

Route::post('{id}/comment', 'HomeController@addComment')->middleware('throttleForGuests');Copy



HTTP 응답 내용 바꾸기
그 다음은 저 스로틀 미들웨어를 통해 예외처리가 되어 429와 메시지를 반환하는 부분을 내 입맛대로 바꿔보자 한다.

app/Exception/Handler.php 파일을 열고 render 메소드를 아래와 같이 개조해준다.

public function render($request, Exception $exception)
{
    // 429 ThrottleRequestsException 처리
    if ($exception instanceof ThrottleRequestsException) {
        $retryAfter = $exception->getHeaders()['Retry-After'] ?? 60;

        return response()->json([
            'message' => "요청이 너무 많습니다. 잠시 후 다시 시도해주세요. (대기 시간: $retryAfter 초)",
        ], 429);
    }

    return parent::render($request, $exception);
}Copy


아까 위에서 말한 헤더에서 넘겨주는 정보를 바탕으로 요청 내용 메시지에 몇 초뒤에 다시 시도라하라는 내용을 추가했다.

이렇게 미들웨어 커스텀을 하고나면 아래와 같은 미들웨어 메시지를 앞단에서 받아서 처리할 수 있다.


끝~

#라라벨 #Laravel #스로틀 #미들웨어 #Throttle #Middleware #Handler
0 개의 댓글
×