๐Ÿ” FastAPI ๋ณด์•ˆ(OAuth2, JWT)

๐Ÿ•’ ์•ฝ 2๋ถ„ ์ฝ๋Š” ๋ฐ ์†Œ์š”๋ฉ๋‹ˆ๋‹ค

์ด์ „ ๊ธ€ ๋ณด๊ธฐ(FastAPI์˜ ์ค‘๊ฐ„์ฒ˜๋ฆฌ์™€ ์ด๋ฒคํŠธ ์‹œ์Šคํ…œ)

โ€“ OAuth2, JWT, ์‚ฌ์šฉ์ž ์ธ์ฆ์„ ์•ˆ์ „ํ•˜๊ณ  ํšจ์œจ์ ์œผ๋กœ ๊ตฌํ˜„ํ•ด๋ณด์ž

“API์˜ ๊ธฐ๋Šฅ๋ณด๋‹ค ์ค‘์š”ํ•œ ๊ฑด ๋ฐ”๋กœ ๋ณด์•ˆ์ด๋‹ค. ์•„๋ฌด๋ฆฌ ์ž˜ ๋งŒ๋“  API๋ผ๋„ ๋ณด์•ˆ์ด ํ—ˆ์ˆ ํ•˜๋ฉด ๋ฌด์šฉ์ง€๋ฌผ์ด๋‹ค.”

FastAPI๋Š” ๋ณด์•ˆ์— ๋Œ€ํ•œ ๊ธฐ๋Šฅ์„ ๋งค์šฐ ๊ฐ•๋ ฅํ•˜๊ฒŒ ์ง€์›ํ•˜๋ฉฐ, ํŠนํžˆ OAuth2 ๋ฐ JWT ๊ธฐ๋ฐ˜ ์ธ์ฆ ์‹œ์Šคํ…œ์„ ํ†ตํ•ด API ์ ‘๊ทผ์„ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค๊ณ„๋˜์–ด ์žˆ๋‹ค.


1. ๐Ÿงญ ์ธ์ฆ(Authentication) vs ๊ถŒํ•œ(Authorization)

๋จผ์ € ๊ฐ€์žฅ ์ค‘์š”ํ•œ ๋‘ ๊ฐœ๋…์„ ๊ตฌ๋ถ„ํ•˜์ž.

  • ์ธ์ฆ(Authentication): ์ด ์‚ฌ์šฉ์ž๊ฐ€ ๋ˆ„๊ตฌ์ธ์ง€ ํ™•์ธํ•˜๋Š” ์ ˆ์ฐจ. ์˜ˆ: ๋กœ๊ทธ์ธ
  • ๊ถŒํ•œ(Authorization): ์ด ์‚ฌ์šฉ์ž๊ฐ€ ํŠน์ • ๋ฆฌ์†Œ์Šค์— ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ์ง€๋ฅผ ๊ฒฐ์ •. ์˜ˆ: ๊ด€๋ฆฌ์ž๋งŒ ๊ธ€ ์‚ญ์ œ ๊ฐ€๋Šฅ

FastAPI๋Š” ์ด ๋‘ ๊ฐœ๋…์„ ๊ตฌ๋ถ„ํ•ด์„œ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•œ๋‹ค.


2. ๐Ÿ”‘ OAuth2๋ž€ ๋ฌด์—‡์ธ๊ฐ€?

OAuth2๋Š” ์ธ์ฆ์„ ์œ„ํ•œ ํ‘œ์ค€ ํ”„๋กœํ† ์ฝœ์ด๋‹ค. ๋ณดํ†ต **”ํ† ํฐ ๊ธฐ๋ฐ˜ ์ธ์ฆ ๋ฐฉ์‹”**์„ ์˜๋ฏธํ•˜๋ฉฐ, ํด๋ผ์ด์–ธํŠธ๋Š” ์‚ฌ์šฉ์ž ์ด๋ฆ„๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ๋กœ ํ† ํฐ์„ ๋ฐœ๊ธ‰๋ฐ›๊ณ  ์ดํ›„ ์š”์ฒญ์— ์ด ํ† ํฐ์„ ํฌํ•จํ•ด ์ธ์ฆ์„ ๋ฐ›๋Š”๋‹ค.

FastAPI๋Š” OAuth2์˜ ํ๋ฆ„์„ ๋‹จ์ˆœํ™”ํ•˜์—ฌ ๋‹ค์Œ ๊ตฌ์กฐ๋ฅผ ์ œ๊ณตํ•œ๋‹ค:

  1. /token ๊ฒฝ๋กœ๋กœ ์‚ฌ์šฉ์ž ์ธ์ฆ ์š”์ฒญ
  2. ์„œ๋ฒ„์—์„œ JWT ํ† ํฐ ๋ฐœ๊ธ‰
  3. ํด๋ผ์ด์–ธํŠธ๋Š” ์ด ํ† ํฐ์„ Authorization ํ—ค๋”์— ๋‹ด์•„ API ์š”์ฒญ

3. ๐Ÿ” JWT(Json Web Token)๋ž€?

JWT๋Š” JSON ๊ธฐ๋ฐ˜์˜ ์„œ๋ช…๋œ ๋ฌธ์ž์—ด ํ† ํฐ์œผ๋กœ, ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ๋‹ด์„ ์ˆ˜ ์žˆ๋‹ค. ๊ตฌ์กฐ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค:

Header.Payload.Signature

์˜ˆ์‹œ:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTYifQ.signature

  • Header: ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์ •๋ณด
  • Payload: ์‚ฌ์šฉ์ž ์ •๋ณด (์˜ˆ: user_id, username ๋“ฑ)
  • Signature: ์œ„ ์ •๋ณด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์„œ๋ช…๋œ ๊ฐ’ (์œ„์กฐ ๋ฐฉ์ง€)

FastAPI๋Š” JWT ํ† ํฐ์„ ๋งŒ๋“ค์–ด์„œ ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธํ•  ๋•Œ๋งˆ๋‹ค ์ด๋ฅผ ํด๋ผ์ด์–ธํŠธ์— ์ „๋‹ฌํ•˜๊ณ , ์ดํ›„ ์š”์ฒญ๋งˆ๋‹ค ํ† ํฐ์„ ๊ฒ€์ฆํ•ด ์ธ์ฆ์„ ์ฒ˜๋ฆฌํ•œ๋‹ค.


4. โš™๏ธ JWT ์ธ์ฆ ํ๋ฆ„ ๊ตฌ์„ฑํ•˜๊ธฐ

  1. ๋กœ๊ทธ์ธ ์š”์ฒญ์„ ๋ฐ›์•„ ์‚ฌ์šฉ์ž์˜ ์ •๋ณด๋ฅผ ๊ฒ€์ฆํ•œ๋‹ค.
  2. ๊ฒ€์ฆ์— ์„ฑ๊ณตํ•˜๋ฉด JWT ํ† ํฐ์„ ์ƒ์„ฑํ•ด์„œ ์‘๋‹ต์œผ๋กœ ๋ณด๋‚ธ๋‹ค.
  3. ํด๋ผ์ด์–ธํŠธ๋Š” ์ด ํ† ํฐ์„ ๋งค API ์š”์ฒญ ์‹œ ํ—ค๋”์— ๋‹ด๋Š”๋‹ค.
  4. ์„œ๋ฒ„๋Š” ํ•ด๋‹น ํ† ํฐ์„ ๊ฒ€์ฆํ•˜๊ณ  ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์‹๋ณ„ํ•œ๋‹ค.

5. ๐Ÿ›  ์‹ค์ œ ์˜ˆ์ œ ์ฝ”๋“œ๋กœ ์ดํ•ดํ•ด๋ณด์ž

ํ•„์š”ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ค์น˜

๋จผ์ € ์•„๋ž˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์„ค์น˜ํ•˜์ž:

pip install python-jose[cryptography] passlib[bcrypt]

์‚ฌ์šฉ์ž ๋ชจ๋ธ ์ •์˜

from pydantic import BaseModel

class User(BaseModel):
    username: str
    password: str

JWT ํ† ํฐ ์ƒ์„ฑ ํ•จ์ˆ˜

from jose import jwt
from datetime import datetime, timedelta

SECRET_KEY = "secret-key"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

def create_access_token(data: dict):
    to_encode = data.copy()
    expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    to_encode.update({"exp": expire})
    return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)

๋กœ๊ทธ์ธ ๋ผ์šฐํ„ฐ

from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
app = FastAPI()

@app.post("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
    if form_data.username != "admin" or form_data.password != "1234":
        raise HTTPException(status_code=401, detail="Invalid credentials")
    token = create_access_token({"sub": form_data.username})
    return {"access_token": token, "token_type": "bearer"}

6. ๐Ÿ” ์‚ฌ์šฉ์ž ์ธ์ฆ ๋ฐ ๊ถŒํ•œ ํ™•์ธ

์ธ์ฆ๋œ ์‚ฌ์šฉ์ž ๊ฐ€์ ธ์˜ค๊ธฐ

from jose import JWTError, jwt

def get_current_user(token: str = Depends(oauth2_scheme)):
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise HTTPException(status_code=401, detail="Invalid token")
        return {"username": username}
    except JWTError:
        raise HTTPException(status_code=401, detail="Invalid token")

์ด ํ•จ์ˆ˜๋ฅผ Depends๋กœ ๋‹ค๋ฅธ ๋ผ์šฐํ„ฐ์— ์ฃผ์ž…ํ•˜๋ฉด, ์ž๋™์œผ๋กœ ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž ์ •๋ณด๊ฐ€ ์ „๋‹ฌ๋œ๋‹ค.

์ธ์ฆ์ด ํ•„์š”ํ•œ API ์˜ˆ์‹œ

@app.get("/users/me")
async def read_users_me(current_user: dict = Depends(get_current_user)):
    return {"user": current_user}

7. ๐Ÿ›ก ๊ถŒํ•œ ๊ด€๋ฆฌ โ€“ ๊ด€๋ฆฌ์ž๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋งŒ๋“ค๊ธฐ

๊ถŒํ•œ์„ ์„ธ๋ถ„ํ™”ํ•˜๋ ค๋ฉด ์‚ฌ์šฉ์ž ์ •๋ณด์— role์ด๋‚˜ is_admin ๊ฐ™์€ ์†์„ฑ์„ ๋„ฃ์–ด์ฃผ๋ฉด ๋œ๋‹ค.

def get_admin_user(current_user: dict = Depends(get_current_user)):
    if current_user.get("username") != "admin":
        raise HTTPException(status_code=403, detail="๊ด€๋ฆฌ์ž๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.")
    return current_user

@app.get("/admin")
async def read_admin_dashboard(admin_user: dict = Depends(get_admin_user)):
    return {"msg": "๊ด€๋ฆฌ์ž ํŽ˜์ด์ง€์ž…๋‹ˆ๋‹ค."}

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด admin์ด๋ผ๋Š” ์‚ฌ์šฉ์ž๋งŒ /admin ๊ฒฝ๋กœ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค.


8. ๐Ÿ”’ ๋ณด์•ˆ์„ ์œ„ํ•œ ํŒ

  • JWT์˜ SECRET_KEY๋Š” ์™ธ๋ถ€์— ๋…ธ์ถœ๋˜์ง€ ์•Š๋„๋ก ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋กœ ๊ด€๋ฆฌํ•˜์ž.
  • ACCESS_TOKEN_EXPIRE_MINUTES๋ฅผ ๋„ˆ๋ฌด ๊ธธ๊ฒŒ ์„ค์ •ํ•˜์ง€ ๋ง์ž. ์ผ๋ฐ˜์ ์œผ๋กœ 15~30๋ถ„์ด ์ ์ ˆํ•˜๋‹ค.
  • ํด๋ผ์ด์–ธํŠธ๋Š” ํ† ํฐ์„ ๋ฐ˜๋“œ์‹œ HTTPS๋กœ ์ „๋‹ฌํ•ด์•ผ ํ•œ๋‹ค.
  • ์‚ฌ์šฉ์ž ์ •๋ณด๋Š” ํ† ํฐ์— ๊ณผ๋„ํ•˜๊ฒŒ ๋‹ด์ง€ ๋ง๊ณ  ์ตœ์†Œํ™”ํ•˜์ž.

9. โœ… ์ •๋ฆฌํ•ด๋ณด์ž

๋ณด์•ˆ์€ ๋ณต์žกํ•˜์ง€๋งŒ, ํ•œ ๋ฒˆ ๊ตฌ์กฐ๋ฅผ ์žก์•„๋‘๋ฉด ์•ˆ์ •์ ์ธ API ์šด์˜์ด ๊ฐ€๋Šฅํ•˜๋‹ค. FastAPI๋Š” OAuth2์™€ JWT ํ† ํฐ์„ ํ†ตํ•œ ์ธ์ฆ ์‹œ์Šคํ…œ์„ ๋น„๊ต์  ์‰ฝ๊ฒŒ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ค€๋‹ค.

๊ธฐ๋Šฅ์—ญํ• ๊ตฌ์„ฑ ์š”์†Œ
OAuth2์ธ์ฆ ํ”„๋กœํ† ์ฝœToken ๋ฐœ๊ธ‰ ๋ฐ ์ธ์ฆ ํ๋ฆ„
JWT์‚ฌ์šฉ์ž ์ •๋ณด ํ† ํฐํ™”Header, Payload, Signature
Depends์ธ์ฆ ๋ฐ ๊ถŒํ•œ ๊ฒ€์‚ฌ ์ฃผ์ž…get_current_user, get_admin_user ๋“ฑ

FastAPI ๊ณต์‹ ๋ฌธ์„œ : https://fastapi.tiangolo.com/ko/

fastapi-logo,fastapi-๊ฐœ๋ฐœ-ํ™˜๊ฒฝ-์„ค์ •, fastapi-์•ฑ-๋งŒ๋“ค๊ธฐ, fastapi-๋ผ์šฐํŒ…, fastapi-request-response, fastapi-์˜ˆ์™ธ-์ฒ˜๋ฆฌ, fastapi-mysql-์—ฐ๋™, fastapi-๋ณด์•ˆ-oauth-jwt