17화 | [AI와 1인기업] 유료 회원 멤버십 구축: Next.js 미들웨어(Middleware) 기반의 특정 제품 및 다운로드 경로 접근 차단 시스템 설계

 

유료 회원 멤버십 구축: Next.js 미들웨어(Middleware) 기반의 특정 제품 및 다운로드 경로 접근 차단 시스템 설계 (17화)

안녕하세요! AI 크루들과 무자본 IT 1인 기업을 꾸려가고 있는 크리에이티브 디렉터 DAVID입니다.

지난 16화에서는 토스페이먼츠와 스트라이프 결제 연동의 핵심인 비동기 웹훅(Webhook) API 핸들러를 구축하여 결제 금액 위변조 해킹을 원천 차단하고, 유저 결제 완료 즉시 30일권의 UUID 보안 라이선스 키를 이메일로 무인 자동 발급하는 '디지털 숍 자동 판매기 파이프라인'을 성공적으로 이식했습니다.

그러나 이 자동 결제 시스템이 마련되었더라도, 만약 비결제 사용자나 인터넷 크롤러 봇이 유료 퀀트 자동 매매 실행 파일(.ex5, .zip)이나 핵심 설정 파일의 다이렉트 다운로드 주소(예: https://artgourmet.cloud/downloads/premium_ea.ex5)를 주소창에 직접 치고 진입했을 때 아무런 차단 장치가 없다면, 공들인 라이선스 게이트는 모조리 무력화됩니다. 오늘 4부 디지털 자산 수호의 대미를 장식할 'Next.js 미들웨어(Middleware) 기반 다운로드 경로 인터셉터' 구축 비법을 투명하게 공개합니다!





1. 왜 일반 리다이렉트가 아니라 '미들웨어(Middleware)'인가?

초보 웹 개발자들이 흔히 시도하는 보안 방식 중 하나는, 특정 페이지 내부의 React useEffect 단에서 사용자의 로그인이나 결제 여부를 체크하여 router.push('/shop')으로 리다이렉트시키는 클라이언트 사이드 인증 방식입니다.

이 방식은 보안 측면에서 다음과 같은 치명적인 파괴적 허점을 지니고 있습니다.

  • 소스코드 유출 (Flickering & Leak): 브라우저 렌더링 속도나 네트워크 환경에 따라, 화면이 다른 페이지로 강제 리다이렉트되기 전 찰나의 순간(0.1초~0.5초) 동안 화면의 실제 유료 다운로드 링크나 정보가 깜빡이며 브라우저에 로드됩니다.
  • 자바스크립트 비활성화 우회: 사용자가 브라우저 개발자 도구(DevTools)에서 JavaScript 실행을 강제로 꺼버리면(Disable JS), 프론트엔드의 리다이렉트 코드(useEffect) 자체가 가동을 멈추어 페이지 내의 모든 유료 파일 주소가 무방비로 노출됩니다.

이와 달리 Next.js의 Edge Runtime에서 가동되는 **'미들웨어(Middleware)'**는 사용자의 요청이 우리 웹 서버에 닿아 브라우저로 응답 HTML/JS가 출발하기 전, 즉 네트워크 레이어의 관문(Gateway)에서 요청 자체를 가로채(Intercept) 권한 검증을 처리합니다. 클라이언트 단의 어떠한 프론트엔드 꼼수도 원천적으로 불가능한 절대 방어선입니다.


2. 무단 침입을 차단하는 다운로드 게이트웨이 필터 체인 설계안

미들웨어가 특정 유료 파일 경로(예: /downloads/:path*)를 감시하며 권한 유무에 따라 트래픽을 처리하는 3단계 라우팅 검증 매커니즘은 다음과 같습니다.

인증 단계 미들웨어 판단 조건 액션 및 리다이렉션 경로
1단계 쿠키 세션 토큰 또는 라이선스 키 파라미터 체크 유저 브라우저 쿠키의 로그인 세션 또는 URL 쿼리 스트링의 결제 영수증 해시값을 0.01초 만에 스캔하여 없으면 즉시 차단합니다.
2단계 서버사이드 데이터베이스 교차 검증 (DB Sync Check) 검출된 라이선스 키가 Firebase DB상에 ACTIVE 상태로 존재하고, 현재 날짜가 만료 기한(expiredAt)을 넘지 않았는지 선제 비교합니다.
3단계 최종 통과(Authorize) 또는 결제창 추방(Paywall) 모든 인증을 통과한 경우에만 파일 다운로드를 허용(NextResponse.next())하고, 위배될 시 NextResponse.redirect('/shop')으로 강제 튕겨냅니다.

3. Next.js Edge Runtime 기반의 미들웨어 실전 소스코드

Next.js 프로젝트 루트 디렉토리에 배치하여 특정 유료 자료 경로를 완전 봉쇄하는 middleware.js (또는 middleware.ts) 실전 소스코드입니다.

import { NextResponse } from 'next/server';

export async function middleware(request) {
    const url = request.nextUrl.clone();
    
    // [1단계] 보호 대상 경로 체크 (/downloads 하위의 모든 파일)
    if (url.pathname.startsWith('/downloads')) {
        // 브라우저 쿠키에서 구매 인증 세션 토큰 획득
        const authCookie = request.cookies.get('ag_session_token')?.value;
        
        // 간편 이메일 라이선스 링크 유입을 위한 URL 쿼리 파라미터 수집
        const licenseParam = url.searchParams.get('key');
        
        // 인증 수단이 전혀 감지되지 않는 경우
        if (!authCookie && !licenseParam) {
            // 결제 상점 페이지(Paywall)로 0.05초 만에 강제 리다이렉트
            url.pathname = '/shop';
            url.searchParams.set('reason', 'unauthorized');
            return NextResponse.redirect(url);
        }

        // [2단계] 라이선스 서버에 비동기 권한 조회 API 호출 (Server-to-Server)
        try {
            const checkKey = authCookie || licenseParam;
            // 로컬 서버의 라이선스 검증 라우트 핸들러 호출
            const verifyRes = await fetch(`${url.origin}/api/license/verify`, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ licenseKey: checkKey, deviceId: 'WEB_DOWNLOAD_GATE' })
            });

            const result = await verifyRes.json();

            // 라이선스가 만료되었거나 비활성 상태인 경우
            if (!result.verified) {
                url.pathname = '/shop';
                url.searchParams.set('reason', result.reason || 'expired');
                return NextResponse.redirect(url);
            }
            
            // [3단계] 검증 성공 시에만 실제 리소스 다운로드 허용
            return NextResponse.next();

        } catch (err) {
            // 서버 에러 발생 시 안전제일(Safe-fail) 원칙에 따라 경로 전면 차단
            url.pathname = '/shop';
            url.searchParams.set('reason', 'server_error');
            return NextResponse.redirect(url);
        }
    }
    
    return NextResponse.next();
}

// 미들웨어를 실행할 정밀 타겟 경로 매칭 (성능 최적화 필수)
export const config = {
    matcher: ['/downloads/:path*']
};

4. 17화를 마치며: 1인 기업 디지털 금고의 완성

서버 통신의 최전방 관문인 Next.js 미들웨어 필터링 체인을 이식함으로써, 이제 무단 주소 조작이나 크롤러 봇의 무차별적인 무단 다운로드 침입으로부터 우리의 핵심 소스코드를 완벽하게 보호할 '디지털 금고의 두꺼운 콘크리트 외벽'을 마침내 완성했습니다. 이로써 4부(디지털 자산 수호)의 막이 안전하고 위대하게 내려앉았습니다.

보안 게이트웨이는 단단히 봉인되었으나, 매번 결제 시마다 이메일 링크로만 파일을 받으면 유저들의 사용 편의성(UX)이 떨어집니다. 사용자가 내 웹사이트에 가입하여 본인의 결제 내역과 현재 발급된 라이선스 키의 유효 기한을 실시간으로 확인하고 제어할 수 있는 '회원 마이페이지' 공간이 연결되어야 합니다.

다음 18화에서는 구글 파이어베이스 인증(Firebase Auth) 기술을 연동하여, 이메일/비밀번호 기반의 초경량 회원가입을 런칭하고, 사용자가 본인의 라이선스 현황을 한눈에 보고 갱신하는 "5부 유료 회원 멤버십 구축: Firebase Auth 연동을 통한 이메일 비밀번호 회원가입 및 마이페이지 라이선스 키 조회 화면 개발"을 정석대로 마스터해 보겠습니다.

미들웨어가 구축해 준 철벽 방어막 위에서 소프트웨어 비즈니스를 안정적으로 자립시켜 보세요. 다음 편에서 뵙겠습니다! 😊🚀


*글쓴이: AI와 함께 무한한 가치를 만들어가는 Creative Director, DAVID.*

댓글

이 블로그의 인기 게시물

프롤로그 | AI 1인 기업 창업 가이드: 코딩 없이 시작하는 IT 스타트업 20부작 로드맵

18화 | [AI와 1인기업] 유료 회원 멤버십 구축: Firebase Auth 연동을 통한 이메일 비밀번호 회원가입 및 마이페이지 라이선스 키 조회 화면 개발

23화 | [AI와 1인기업] AI 비즈니스 에이전트 이식: OpenAI Assistants RAG 엔진 기반의 텔레그램 무인 기술지원(CS) 자동 챗봇 구축