C.W.K.
Stream
Lesson 04 of 06 · published

Login 엔드포인트 — Brute Force 가 시작되는 곳

~15 min · login, form, rate-limit

Level 0Greenhorn
0 XP0/53 lessons0/14 achievements
0/100 XP to next level100 XP to go0% complete

Login 엔드포인트는 공격자가 추측할 수 있는 유일한 곳. 나머지 다는 login 엔드포인트가 발급한 session 으로 게이트. 이 한 라우트 제대로 하면 나머지 layer 따라와.

각 줄이 중요한 이유

방어하는 것
Pre-check blacklistbcrypt 비용 지불 전; 알려진 bad IP 에 CPU 절약
Pre-check attempt count6번째 시도가 추측 성공 전에 차단
실패 시 constant-time 지연oracle 로서 timing 제거 (PIN 길이, bcrypt 비용)
실패 시 증가, 성공 시 clear몇 번 정당한 오타로 영원히 lockout 안 되게 counter reset
HttpOnly, Secure, SameSite=strict표준 쿠키 강화 (Track 2)

Code

POST /login — canonical 솔로 개발자 모양·python
import asyncio
from fastapi import FastAPI, Form, Request
from fastapi.responses import RedirectResponse, Response

app = FastAPI()

@app.post("/login")
async def login(request: Request, pin: str = Form(...)):
    ip = client_ip(request)

    # Pre-check: blacklist (defense in depth; middleware 도 차단)
    if is_blacklisted(ip):
        return Response("Forbidden", status_code=403)

    # Pre-check: 이미 너무 많이 실패?
    if attempt_count(ip) >= max_retry():
        blacklist(ip, reason="exceeded_retry")
        return Response("Locked out", status_code=429)

    # PIN 검증
    cfg = load_config()
    if not verify_pin(pin, cfg["pin_hash"]):
        increment_attempt(ip)
        # Constant-time 응답: 'PIN 틀림' 과 '없는 사용자' 누출 X
        await asyncio.sleep(0.5)        # timing 공격도 무뎌짐
        return Response("Invalid PIN", status_code=401)

    # 성공: counter clear, session 발급
    clear_attempts(ip)
    token    = create_session(ip)
    response = RedirectResponse("/", status_code=302)
    response.set_cookie(
        "session", token,
        max_age=cfg["session_seconds"],
        httponly=True, secure=True, samesite="strict", path="/"
    )
    return response
Login 폼 — 중요한 작은 UX 디테일·html
<form method="POST" action="/login" autocomplete="off">
  <input
    name="pin"
    type="password"
    inputmode="numeric"
    pattern="[0-9]*"
    maxlength="4"
    autofocus
    autocomplete="off"
  />
  <button type="submit">Unlock</button>
</form>

External links

Exercise

/login POST 엔드포인트와 최소 HTML 폼 추가. 성공 경로 (맞는 PIN), 실패 경로 (틀린 PIN, security_attempts 의 counter 증가 봐), lockout (5+ 틀린 시도 → blacklisted, 429) 테스트. 운동은 세 상태 다 wire — 다음 트랙들이 튜닝.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.