๐ ์ฝ 2๋ถ ์ฝ๋ ๋ฐ ์์๋ฉ๋๋ค
์ด์ ๊ธ ๋ณด๊ธฐ(FastAPI์ ์ค๊ฐ์ฒ๋ฆฌ์ ์ด๋ฒคํธ ์์คํ )
โ OAuth2, JWT, ์ฌ์ฉ์ ์ธ์ฆ์ ์์ ํ๊ณ ํจ์จ์ ์ผ๋ก ๊ตฌํํด๋ณด์
“API์ ๊ธฐ๋ฅ๋ณด๋ค ์ค์ํ ๊ฑด ๋ฐ๋ก ๋ณด์์ด๋ค. ์๋ฌด๋ฆฌ ์ ๋ง๋ API๋ผ๋ ๋ณด์์ด ํ์ ํ๋ฉด ๋ฌด์ฉ์ง๋ฌผ์ด๋ค.”
FastAPI๋ ๋ณด์์ ๋ํ ๊ธฐ๋ฅ์ ๋งค์ฐ ๊ฐ๋ ฅํ๊ฒ ์ง์ํ๋ฉฐ, ํนํ OAuth2 ๋ฐ JWT ๊ธฐ๋ฐ ์ธ์ฆ ์์คํ ์ ํตํด API ์ ๊ทผ์ ์ ์ดํ ์ ์๋๋ก ์ค๊ณ๋์ด ์๋ค.
1. ๐งญ ์ธ์ฆ(Authentication) vs ๊ถํ(Authorization)
๋จผ์ ๊ฐ์ฅ ์ค์ํ ๋ ๊ฐ๋ ์ ๊ตฌ๋ถํ์.
- ์ธ์ฆ(Authentication): ์ด ์ฌ์ฉ์๊ฐ ๋๊ตฌ์ธ์ง ํ์ธํ๋ ์ ์ฐจ. ์: ๋ก๊ทธ์ธ
- ๊ถํ(Authorization): ์ด ์ฌ์ฉ์๊ฐ ํน์ ๋ฆฌ์์ค์ ์ ๊ทผ ๊ฐ๋ฅํ์ง๋ฅผ ๊ฒฐ์ . ์: ๊ด๋ฆฌ์๋ง ๊ธ ์ญ์ ๊ฐ๋ฅ
FastAPI๋ ์ด ๋ ๊ฐ๋ ์ ๊ตฌ๋ถํด์ ์ฒ๋ฆฌํ ์ ์๋ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ค.
2. ๐ OAuth2๋ ๋ฌด์์ธ๊ฐ?
OAuth2๋ ์ธ์ฆ์ ์ํ ํ์ค ํ๋กํ ์ฝ์ด๋ค. ๋ณดํต **”ํ ํฐ ๊ธฐ๋ฐ ์ธ์ฆ ๋ฐฉ์”**์ ์๋ฏธํ๋ฉฐ, ํด๋ผ์ด์ธํธ๋ ์ฌ์ฉ์ ์ด๋ฆ๊ณผ ๋น๋ฐ๋ฒํธ๋ก ํ ํฐ์ ๋ฐ๊ธ๋ฐ๊ณ ์ดํ ์์ฒญ์ ์ด ํ ํฐ์ ํฌํจํด ์ธ์ฆ์ ๋ฐ๋๋ค.
FastAPI๋ OAuth2์ ํ๋ฆ์ ๋จ์ํํ์ฌ ๋ค์ ๊ตฌ์กฐ๋ฅผ ์ ๊ณตํ๋ค:
/token
๊ฒฝ๋ก๋ก ์ฌ์ฉ์ ์ธ์ฆ ์์ฒญ- ์๋ฒ์์ JWT ํ ํฐ ๋ฐ๊ธ
- ํด๋ผ์ด์ธํธ๋ ์ด ํ ํฐ์ 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 ์ธ์ฆ ํ๋ฆ ๊ตฌ์ฑํ๊ธฐ
- ๋ก๊ทธ์ธ ์์ฒญ์ ๋ฐ์ ์ฌ์ฉ์์ ์ ๋ณด๋ฅผ ๊ฒ์ฆํ๋ค.
- ๊ฒ์ฆ์ ์ฑ๊ณตํ๋ฉด JWT ํ ํฐ์ ์์ฑํด์ ์๋ต์ผ๋ก ๋ณด๋ธ๋ค.
- ํด๋ผ์ด์ธํธ๋ ์ด ํ ํฐ์ ๋งค API ์์ฒญ ์ ํค๋์ ๋ด๋๋ค.
- ์๋ฒ๋ ํด๋น ํ ํฐ์ ๊ฒ์ฆํ๊ณ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ์๋ณํ๋ค.
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/
