IP 위치에 따라 접근을 금지하기

저자:Lisa Farrell · 2026-01-18

IP 위치 기반 프런트엔드 접근 제어 솔루션: 페이지가 로드될 때 https://my.ipin.io/info 를 요청하여 방문자의 국가 코드(country)를 가져옵니다. country가 TW(또는 사용자가 설정한 다른 지역)로 식별되면, 페이지는 정상 콘텐츠 제공을 더 이상 진행하지 않고 프런트엔드에서 접근 흐름을 직접 종료합니다.

“지역별로 접근을 제한”하는 요구는 사실 강력한 보안(해커 방어 같은 것)을 만들기 위한 목적이라기보다, 더 흔한 프런트엔드 시나리오에 해당합니다:

  • 일부 국가/지역에는 서비스를 일시적으로 제공하지 않음(규정 준수, 저작권, 결제, 물류 등의 이유)
  • 사용자가 페이지에 들어오자마자 지역을 먼저 판별한 뒤, 콘텐츠/버튼/다운로드 링크를 보여줄지 결정
  • 마케팅 캠페인이 특정 지역만 대상으로 진행되어, 다른 지역에는 “서비스 범위 밖” 또는 “사용 불가”를 즉시 안내

이 방식은 프런트엔드에 특히 적합합니다: 브라우저가 GET https://my.ipin.io/info 를 직접 요청하면, API는 “요청을 발생시킨 IP”를 기준으로 지리 정보를 반환합니다. 예:

{"ip":"185.220.236.7","country":"TW","region":"Taiwan","city":"Taipei"}

그 다음 `country === "TW"` 만 판단하면 “접근 금지” 동작을 수행할 수 있습니다(예: 접근 금지 안내 표시, 404 문구 출력, 또는 사용자 정의 오류 페이지 구조로 원래 페이지를 덮어쓰기). 여기서 핵심은 “리다이렉트”를 논의하는 것이 아니라, 접근 결과를 오류/거부로 명확히 보여주어 사용자가 보는 화면을 “접근 불가”의 최종 상태로 만드는 것입니다.

1) 로직 및 동작 원리(2문단)

첫 번째 문단: 흐름
사용자가 웹페이지를 열기 → 브라우저가 즉시 https://my.ipin.io/info 요청 → `country` 획득 → 블랙리스트(예: `TW`)에 해당하면 페이지의 후속 표시를 즉시 차단: 정상 콘텐츠를 더 이상 렌더링하지 않고 “접근 금지 / 404 / 사용자 정의 오류” 같은 최종 상태 정보를 바로 출력; 해당하지 않으면 페이지 기능과 콘텐츠를 정상적으로 계속 로드합니다.

두 번째 문단: 왜 이 API에 프런트엔드가 적합한가
브라우저가 서드파티 API를 직접 요청하기 때문에, 서드파티가 보는 값은 방문자의 “공인 IP”이며, 반환되는 국가/지역은 사용자 본인에 대응합니다—이것이 바로 사용자가 원하는 효과입니다.

2) 코드 구현(가능한 한 단순하게, 프런트엔드 중심)

2.1 가장 간단함: 일치하면 “접근 금지”(권장)

이 코드를 페이지 <head>에 넣으세요(가능하면 앞쪽일수록 좋습니다). 일치 시 페이지 내용을 덮어쓰고, 명확한 “접근 금지” 메시지를 표시합니다. 문구는 규정 준수 안내나 서비스 범위 안내로 교체할 수 있습니다.

예시 A: country === "TW" 이면 접근 금지

<script>
(async function () {
  try {
    const info = await fetch("https://my.ipin.io/info").then(r => r.json());
    if (info.country === "TW") {
      document.documentElement.innerHTML =
        "<head><title>403 Forbidden</title></head>" +
        "<body style='font-family:system-ui;padding:40px'>" +
        "<h1>403 접근 금지</h1>" +
        "<p>현재 지역에서는 서비스를 이용할 수 없습니다.</p>" +
        "</body>";
    }
  } catch (e) {
    // 조회 실패 시 기본 허용(기본 차단으로 바꿀 수 있음)
  }
})();
</script>

예시 B: TW가 아니면 접근 금지(TW만 허용)

<script>
(async function () {
  try {
    const info = await fetch("https://my.ipin.io/info").then(r => r.json());
    if (info.country !== "TW") {
      document.documentElement.innerHTML =
        "<head><title>403 Forbidden</title></head>" +
        "<body style='font-family:system-ui;padding:40px'>" +
        "<h1>403 접근 금지</h1>" +
        "<p>이 페이지는 지정된 지역에서만 이용할 수 있습니다.</p>" +
        "</body>";
    }
  } catch (e) {
    // 조회 실패 시 기본 허용(기본 차단으로 바꿀 수 있음)
  }
})();
</script>

예시 C: TW, US 등 접근 금지(블랙리스트)

<script>
(async function () {
  const blocked = ["TW", "US"]; // 목록 중 하나라도 일치하면 차단
  try {
    const info = await fetch("https://my.ipin.io/info").then(r => r.json());
    if (blocked.includes(info.country)) {
      document.documentElement.innerHTML =
        "<head><title>403 Forbidden</title></head>" +
        "<body style='font-family:system-ui;padding:40px'>" +
        "<h1>403 접근 금지</h1>" +
        "<p>현재 지역에서는 서비스를 이용할 수 없습니다.</p>" +
        "</body>";
    }
  } catch (e) {}
})();
</script>

2.2 404 Not Found 출력(“사용 불가/존재하지 않음” 방식)

어떤 제품은 특정 지역에서 “제한됨”을 명시하기보다 “페이지가 없는 것처럼” 보이게 하길 원합니다. 이런 경우 404 의미의 오류 페이지 구조로 원래 페이지 내용을 덮어쓸 수 있습니다. 참고: 이것은 여전히 프런트엔드 표시이므로 서버가 반환하는 HTTP 상태 코드를 실제로 바꾸지는 않지만, “페이지 접근 경험” 측면에서는 충분히 직관적입니다.

<script>
(async function () {
  try {
    const { country } = await fetch("https://my.ipin.io/info").then(r => r.json());
    if (country === "TW") {
      document.documentElement.innerHTML =
        "<head><title>404 Not Found</title></head>" +
        "<body style='font-family:system-ui;padding:40px'>" +
        "<h1>404 Not Found</h1>" +
        "<p>The requested URL was not found on this server.</p>" +
        "</body>";
    }
  } catch (e) {}
})();
</script>

2.3 배열로 블랙리스트 관리(실제 업무에 더 가까움)

실제로는 한 지역만 막는 경우보다 블랙리스트 목록을 관리하는 경우가 많습니다. 아래 예시는 여전히 단순하게 유지하며, 목록 중 하나라도 일치하면 즉시 “접근 금지”를 출력합니다.

<script>
(async function () {
  const blocked = ["TW"]; // 차단할 국가/지역 코드
  try {
    const info = await fetch("https://my.ipin.io/info").then(r => r.json());
    if (blocked.includes(info.country)) {
      document.documentElement.innerHTML =
        "<head><title>403 Forbidden</title></head>" +
        "<body style='font-family:system-ui;padding:40px'>" +
        "<h1>403 접근 금지</h1>" +
        "<p>현재 지역에서는 서비스를 이용할 수 없습니다.</p>" +
        "</body>";
    }
  } catch (e) {}
})();
</script>

2.4 React(가장 짧게 사용 가능: 일치 시 오류 페이지 표시)

import { useEffect, useState } from "react";

export default function App() {
  const [blocked, setBlocked] = useState(false);

  useEffect(() => {
    (async () => {
      try {
        const info = await fetch("https://my.ipin.io/info").then(r => r.json());
        if (info.country === "TW") setBlocked(true);
      } catch (e) {}
    })();
  }, []);

  if (blocked) {
    return (
      <div style={{ fontFamily: "system-ui", padding: 40 }}>
        <h1>403 접근 금지</h1>
        <p>현재 지역에서는 서비스를 이용할 수 없습니다.</p>
      </div>
    );
  }

  return <div>...</div>;
}

2.5 Vue(동일하게 가장 짧게: 일치 시 오류 내용 표시)

<script>
export default {
  data() {
    return { blocked: false };
  },
  async mounted() {
    try {
      const info = await fetch("https://my.ipin.io/info").then(r => r.json());
      if (info.country === "TW") this.blocked = true;
    } catch (e) {}
  }
}
</script>

<template>
  <div v-if="blocked" style="font-family:system-ui;padding:40px">
    <h1>403 접근 금지</h1>
    <p>현재 지역에서는 서비스를 이용할 수 없습니다.</p>
  </div>

  <div v-else>...</div>
</template>

3) WordPress / Joomla / Magento / Shopify 등에서 구현(프런트엔드 방식 + 코드)

접근 방식은 모두 같습니다: “초간단 JS”를 사이트 전체 공용 Head(또는 레이아웃 파일)에 삽입합니다. 목표가 “프런트엔드 제어 + 오류 표시”이므로, 스크립트가 충분히 일찍 실행되기만 하면 본문 렌더링 전에 지역 판별을 끝낼 수 있고, 일치할 때는 “접근 금지/404/오류 안내”의 최종 상태를 그대로 표시할 수 있습니다.

아래 스니펫을 통일해서 쓰는 것을 권장합니다(가장 단순한 “접근 금지” 버전):

<script>
(async function () {
  try {
    const info = await fetch("https://my.ipin.io/info").then(r => r.json());
    if (info.country === "TW") {
      document.documentElement.innerHTML =
        "<head><title>403 Forbidden</title></head>" +
        "<body style='font-family:system-ui;padding:40px'>" +
        "<h1>403 접근 금지</h1>" +
        "<p>현재 지역에서는 서비스를 이용할 수 없습니다.</p>" +
        "</body>";
    }
  } catch (e) {}
})();
</script>

3.1 WordPress

  • 방법 A(가장 쉬움): 헤더/푸터 스크립트 삽입 플러그인을 사용해 Header 영역에 붙여넣기(전체 적용)
  • 방법 B: 테마 파일 `header.php`</head> 앞에 스크립트 삽입

3.2 Joomla! / Joomla

  • 현재 템플릿의 `index.php` 에서 <head> 를 찾아 </head> 앞에 붙여넣기
  • 또는 백엔드/템플릿에서 head에 커스텀 코드/HTML을 넣을 수 있는 위치를 활용(템플릿마다 다름)

3.3 Magento(프런트엔드 주입 방식)

  • 표시 레벨에서만 막고 싶다면, 테마의 전역 head(theme/layout의 head 영역)에 스크립트를 추가
  • Magento 백엔드에도 “HTML Head”와 유사한 설정이 있는 경우가 많으며(버전/테마별 차이), 목표는 전체 <head> 에 주입하는 것입니다

> 목표가 “서버 측 코드를 최대한 피하기”이므로, 여기서는 XML/모듈 방식은 확장하지 않습니다. head에 넣을 수 있으면 동작합니다.

3.4 Shopify

  • Online Store → Themes → Edit code
  • `theme.liquid` 을 찾아 </head> 앞에 붙여넣기(전체 적용)

3.5 기타 일반적인 시스템(공통 패턴)

  • 전역 <head> 편집이 가능한 시스템: head의 가능한 앞쪽에 스크립트를 배치
  • 특정 페이지만 제한: 해당 페이지 템플릿에만 스크립트를 추가

4) 요약

이 글의 핵심은 한 문장으로 정리할 수 있습니다:

> 브라우저가 https://my.ipin.io/info 를 요청해 { country } 를 얻고, country === "TW" 인 경우 “접근 금지 / 404 / 사용자 정의 오류” 형태로 페이지 접근을 즉시 종료함으로써, IP 지역 기반 프런트엔드 접근 금지를 구현한다.

구현 비용이 낮고 적용이 빠르며, “규정 준수 안내 / 지역별 이용 불가 안내 / 프런트엔드 트래픽 제어”에 적합합니다. 많은 비즈니스에서는 프런트엔드 레벨의 “오류 표시”만으로도 제품/운영 요구를 충족합니다. 사용자는 자신의 지역에서 페이지가 불가하다는 것을 명확히 인지하고, 페이지의 주요 콘텐츠와 진입점이 더 이상 노출되지 않습니다. 더 엄격하게 “우회 불가”를 원한다면 서버 또는 CDN 레벨의 차단을 추가로 겹치면 됩니다.

5) 자주 묻는 질문(FAQ)

Q1: 프런트엔드 차단으로 정말 “접근 금지”가 되나요?

더 정확히 말하면, 표시 레이어에서 명확한 거부 결과(예: 403/404/오류 메시지 페이지)를 보여주어 일반 사용자가 페이지 기능을 계속 사용하지 못하게 만들 수 있습니다. 다만 숙련된 사용자는 JavaScript를 비활성화하거나 스크립트를 수정할 수 있으므로, 우회 불가능한 보안 장벽은 아닙니다. “절대 차단”이 필요하면 서버/CDN 레벨의 차단을 추가하세요. 규정 준수 안내나 이용 불가 안내가 목적이라면 프런트엔드 차단만으로도 충분한 경우가 많습니다.

Q2: 왜 스크립트를 <head>에, 그것도 가능한 앞쪽에 넣나요?

더 빨리 판별할수록 본문이 렌더링되기 전에 접근 흐름을 종료할 수 있어, “잠깐 보였다가 막히는” 깜빡임을 줄일 수 있습니다. 또한 불필요한 리소스(스크립트, 이미지, 컴포넌트) 로딩을 피할 수 있어 UX와 성능에 모두 유리합니다.

Q3: https://my.ipin.io/info 요청이 실패하면 어떻게 하나요?

가장 흔한 전략은 “실패 시 기본 허용”입니다. 외부 API 장애로 정상 사용자가 오탐 차단되는 것을 피하기 위함입니다. 반대로 “실패 시 기본 차단”은 강한 컴플라이언스 환경에 적합합니다. 비즈니스 리스크에 따라 선택할 수 있습니다. 어느 쪽이든 로직은 단순하게: country 를 얻으면 판단하고, 얻지 못하면 기본 정책을 따르도록 유지하는 것이 좋습니다.

Q4: CORS 문제가 생길 수 있나요?

가능성이 있습니다. API가 브라우저의 크로스 오리진 접근을 허용하지 않으면 fetch 가 브라우저에 의해 차단됩니다. 이 경우 API 측에서 CORS를 활성화(예: 당신의 도메인을 허용)해야 브라우저가 JSON을 읽을 수 있습니다. “프런트엔드 중심” 방식에서는 CORS가 가장 먼저 확인해야 할 포인트입니다.

Q5: country 값은 어떤 표준을 따르나요?

일반적으로 2글자 국가/지역 코드(대개 ISO 3166-1 alpha-2)입니다. TW 는 대표적인 예입니다. 실제 사용 시에는 대문자 코드를 일관되게 사용하고, 블랙리스트도 같은 형식으로 유지하면 대소문자 차이로 인한 판정 실패를 피할 수 있습니다.

Q6: “특정 국가 차단”에서 “특정 국가만 허용”으로 바꾸려면?

로직을 반대로 하면 됩니다:
- allowlist: ["US","JP"] 만 허용하고 나머지는 “접근 금지/오류 표시”
- blocklist: ["TW"] 만 차단하고 나머지는 허용
둘 다 본질적으로 country 가 리스트에 포함되는지 검사하는 것이며, 차이는 기본 동작에 있습니다.