홈으로 돌아가기

동적 라우트 net/http: DeadDrop 구현

이 글은 DeadDrop 서비스를 위한 순수 net/http의 동적 라우트와 쿠키 인증 구현을 분석합니다. 수동 경로 파싱, HMAC 토큰, 유스케이스 아키텍처. 테스트와 함께 전체 사이클.

net/http: DeadDrop의 동적 라우트와 쿠키 인증
Advertisement 728x90

net/http에서 동적 라우팅과 쿠키 인증 구현: DeadDrop 서비스 구축

순수 net/http 시리즈를 이어가며 DeadDrop 서비스를 위한 동적 라우팅을 구현합니다. 사용자는 /secret/{id}와 같은 링크를 통해 비밀을 확인할 수 있습니다. Go 1.22 이전 표준 mux는 경로 매개변수를 지원하지 않으므로 직접 파서를 작성합니다.

핸들러는 /secret/에 등록되며(뒤에 슬래시 포함), 이는 모든 하위 경로를 캡처합니다. ID는 r.URL.Path에서 수동으로 추출합니다.

func extractIDFromPath(path string) (string, bool) {
    const prefix = "/secret/"

    if !strings.HasPrefix(path, prefix) {
        return "", false
    }

    id := strings.TrimPrefix(path, prefix)
    if id == "" || strings.Contains(id, "/") {
        return "", false
    }

    return id, true
}

이 함수는 접두사를 확인하고 제거한 후 유효하지 않은 ID(비어 있거나 / 포함)를 제외합니다. 이는 잘못된 요청에 대한 기본적인 보호를 제공합니다.

Google AdInline article slot

의존성 주입을 통한 SecretHandler

의존성 주입을 위한 핸들러 구조체를 생성합니다:

type SecretHandler struct {
    // 의존성 주입을 위한 자리 표시자
}

func NewSecretHandler() *SecretHandler {
    return &SecretHandler{}
}

func (h *SecretHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    id, ok := extractIDFromPath(r.URL.Path)
    if !ok {
        http.NotFound(w, r)
        return
    }
    w.Header().Set("Content-Type", "text/plain; charset=utf-8")
    w.WriteHeader(http.StatusOK)
    w.Write([]byte("요청된 비밀 ID: " + id))
}

router.go에 등록: mux.Handle("/secret/", secretHandler). 경계 사례 테스트:

  • /secret/12345/ → 404
  • /secret/ → 404
  • /secret/axQ/233 → 404
  • /secret/ad31H234 → ID와 함께 OK

비밀 조회 사용 사례

요청 및 응답 정의:

Google AdInline article slot
type GetSecretRequest struct {
    ID       string
    Password string
}

type GetSecretResponse struct {
    ID        string
    Message   string
    FileName  string
    FileExt   string
    HasFile   bool
    ExpiresAt time.Time
}

사용 사례 로직:

  • 저장소에서 비밀 조회
  • 만료 확인(time.After()), 만료된 항목 삭제
  • CheckPasswordHash를 통한 비밀번호 확인
  • 데이터 또는 오류 반환

쿠키 인증

파일 다운로드를 보호하기 위해 쿠키를 통한 인증을 구현합니다. 비밀번호 확인 성공 후 암호화된 액세스 토큰이 포함된 세션 쿠키를 설정합니다.

  • 장점: 상태 비저장, 확장 가능, 세션 저장소 불필요
  • 쿠키 매개변수: HttpOnly, Secure, SameSite=Strict

/download/{id} 요청 시:

Google AdInline article slot
  • 경로에서 ID 추출
  • auth_token 쿠키 읽기
  • 토큰 디코딩 및 확인(ID + 비밀번호 해시 + 논스)
  • 유효한 경우 파일 제공

액세스 토큰 구조

토큰은 base64(ID + ":" + hash + ":" + nonce) 형태로 구성되며, 여기서:

  • hash = HMAC-SHA256(ID + password, secretKey)
  • nonce—재전송 공격 방지를 위한 일회용 숫자

예시 검증:

func validateDownloadToken(id, token string) bool {
    // 토큰 디코딩
    // 해시 및 논스 추출
    // HMAC 재계산
    // 비교
    return true // 또는 false
}

전체 비밀 워크플로우

  • 생성: POST /create → ID 생성, 비밀번호 해시, 저장
  • 조회: GET /secret/{id} → 비밀번호 입력 양식
  • 인증: POST 비밀번호 → 쿠키 + 리디렉션
  • 다운로드: GET /download/{id} → 쿠키 확인 → 파일
  • 정리: TTL 만료 → 삭제

UI 프로토타입: 비밀번호 필드와 "열기" 버튼이 있는 페이지. 성공 후 콘텐츠 및 파일 링크 제공.

라우팅 테스트

| 테스트 경로 | 기대 결과 | 결과 |

|-----------|-------------|--------|

| /secret/123/ | 404 | 404 |

| /secret/ | 404 | 404 |

| /secret/id/with/slash | 404 | 404 |

| /secret/valid123 | OK: "ID: valid123" | OK |

경계 사례는 파서 보안을 다룹니다.

핵심 포인트

  • 동적 라우팅: 타사 라우터 없이 r.URL.Path 수동 파싱
  • 보안: ID 검증, 슬래시 확인, 비밀 TTL
  • 인증: 세션 대신 HMAC 토큰이 포함된 쿠키
  • 아키텍처: 사용 사례 + 의존성 주입, 핸들러 외부의 깔끔한 로직
  • 확장성: 상태 비저장, 클러스터링 준비 완료

— Editorial Team

Advertisement 728x90

다음 읽기