bot-protection-botd 트러블 슈팅

cansweep·2022년 7월 27일
1
post-thumbnail

👀 botd 적용 방식 선택 이유

이전 포스팅에서 적었듯 botd를 적용하는 방식은 두 가지가 있다.

1. script로 적용하기 => botd를 적용할 곳에 Script를 추가한다.
2. middleware로 적용하기 => middleware.ts에 botd를 사용해 일관적으로 처리한다.

이 중 middleware를 사용하는 것으로 결정했다.

middleware를 선택한 이유는 다음과 같다.

  • 봇을 방지할 페이지들의 처리가 동일함.
    -> 페이지 별로 처리하는 로직을 따로 두지 않고 이를 middleware에서 처리하기 위해서.
  • script를 사용할 경우 botd에 요청을 보내 봇 판별 결과를 가져오기까지 봇을 방지해야 할 페이지가 노출됨.
    -> 잠깐의 시간이지만 그 사이에 무슨 일이 일어날 지 모르기 때문에 이것도 방지하고자 함.

⌨️ 구현

example에서 lib/botd/index.ts의 내용을 일부분 수정했고 middleware.ts를 다음과 같이 작성했다.

//middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { botdEdge } from "@lib/botd";
import { ROUTE } from "@utils/constant";

export const config = {
  matcher: ["/login", "/register"],
};

export default async function middleware(req: NextRequest) {
  const res = await botdEdge(req, {
    useRequestId: false,
  });
  
  if (res && res.status !== 200) {
    // Bot detected!
    req.nextUrl.pathname = ROUTE.BLOCK.link;
    const rewrite = NextResponse.rewrite(req.nextUrl);
    // Move Botd headers to the rewrite response
    res.headers.forEach((v, k) => rewrite.headers.set(k, v));
    return rewrite;
  }
  return res;
}

봇을 방지하고자 하는 페이지인 /login/register 페이지에 접근할 때만 bot-detection 요청을 보내 봇 판별 후 봇이라면 /blocked로 보내고 봇이 아니라면 정상적인 url로 보낸다.

⚠️ 문제 : bot-detection 요청이 여러 번 발생

문제 설명

problem

그런데 /login을 주소창에 입력 후 /login 페이지로 이동하고자 하면 처음 주소창에 입력될 때, 그리고 잠시 후 같은 url으로 요청이 가 총 2번의 요청이 발생한다.

주소창에 입력 후 바로 botd로 요청이 갈 때는 botd가 봇이라고 판단해 /blocked 페이지로 보내고 이후 두 번째 요청에서는 정상적으로 봇이 아님을 인지하여 다시 /login 페이지로 이동하고 있다.

따라서 봇이 아님에도 불구하고 /blocked 페이지를 한 번 거친 후 /login 페이지로 이동하게 되는 것이다.

원인

botd로 요청을 보내기 위한 바디는 req.headers를 토대로 만든 값이다.
따라서 req.headers를 뜯어보고 왜 같은 url로의 2번의 요청이 일어나는지 살펴봤다.

secmode

req.headers를 확인해본 결과 sec-fetch-mode가 navigate인 요청이 먼저 발생하고 그 이후 sec-fetch-mode가 cors인 요청이 발생해 총 2건의 요청이 botd에 전달되고 있었다.

해결

sec-fetch-mode가 navigate인 요청에서는 정상적인 결과를 얻을 수 없었기에 처음 발생하는 요청에 대해서는 bot-detection을 사용할 필요가 없다고 판단했다.

// sec-fetch-mode가 cors가 아닐 경우에는 botd 요청 안 함
if (req.headers.get("sec-fetch-mode") !== "cors") return;

따라서 bot-detection 요청을 보내기 전 req.headers의 sec-fetch-mode가 cors인지 확인하고 cors일 경우에만 bot-detection을 하도록 해 원하는 결과를 얻을 수 있었다.

+++
원래는 sec-fetch-mode가 navigate일 경우에만 return하도록 했는데 이 경우 chrome이나 chrome devtool의 device toolbar 환경에서는 정상 작동하지만 배포 후 실제 모바일 환경에서는 같은 에러가 남아있었다.

따라서 안전하게 처리하기 위해서 sec-fetch-mode가 cors인지 아닌지를 조건으로 설정했다.

profile
하고 싶은 건 다 해보자! 를 달고 사는 프론트엔드 개발자입니다.

0개의 댓글