OAuth2와 jwt를 활용한 인증 구현
개념
OAuth2 (Open Authorization 2.0)
OAuth2 (Open Authorization 2.0)은 계정 정보를 직접 공유하지 않고 서비스 간 정보를 안전하게 공유할 수 있도록 한다. 또한 비밀번호 공유 없이 권한 부여를 수행할 수 있으며, Access Token을 통해 API 요청에 대한 인증을 처리한다.
- Resource Owner : 사용자, 데이터를 소유하는 주체
- Client : 애플리케이션, 사용자를 대신해 API 요청을 수행
- Authorization Server : OAuth2 인증을 수행하고 토큰을 발급하는 서버
- Resource Server : API 요청을 처리하는 서버 (FastAPI)
- Access Token : 인증된 사용자만 API를 사용할 수 있도록 하는 키
OAuth2의 작동 과정에 대해 알고 싶다면, "OAuth2.0(Open Authorization 2.0) 정리"를 참고하면 좋을 것 같다.
JWT (Json Web Token)
JWT (Json Web Token)는 사용자 정보를 안전하게 전달하기 위한 토큰 기반 인증 방식이다. JSON 기반이라 가볍고 빠르며, OAuth2의 Access Token으로 많이 사용된다. JWT의 구성 요소인 Payload는 디코딩이 가능하기 때문에 비밀번호와 같은 민감한 데이터는 포함하면 안 된다. 또한 Signature을 통해 토큰의 무결성을 검증하여 토큰이 수정되지 않았는지 확인이 가능하다.
- Header.Payload.Signature : "."으로 구분된 3개의 부분으로 구성
- Header : 사용할 알고리즘 정의
- Payload : 사용자 정보와 토큰의 메타데이터 등을 포함
- Signature : Header + Payload를 암호화한 서명 (SECRET_KEY 사용)
OAuth2와 JWT를 활용한 인증 과정 (FastAPI)
SECRET_KEY 및 Token 설정
from datetime import datetime, timedelta
from typing import Optional
from jose import JWTError, jwt
from passlib.context import CryptContext
# SECRET_KEY 설정
SECRET_KEY = "supersecretkey123456789"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
# 비밀번호 해싱 설정 (bcrypt 사용)
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password)
def get_password_hash(password):
return pwd_context.hash(password)
JWT 토큰 생성 및 검증 함수
- JWT 토큰 생성 : 만료 기한 (expire, exp)을 "현재 시간 + 설정한 시간"으로 설정 후 encode
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
to_encode = data.copy()
expire = datetime.utcnow() + (expires_delta or timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES))
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
- JWT 토큰 검증 : 파라미터로 받은 token을 decode 하여 유효 여부 판단
def verify_token(token: str):
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
return payload # 유효한 경우 payload 반환
except JWTError:
return None # 유효하지 않으면 None 반환
FastAPI 엔드포인트
가상의 사용자 데이터 (fake_users_db)를 사용해 token을 생성하고, 검증하는 API를 생성하였다.
- oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
- 로그인 시 토큰을 받기 위한 경로 정의 (/token)
- protected_route에서 Depends(oauth2_scheme)를 통해 토큰을 자동으로 가져오도록 설정
- 즉, 사용자는 로그인 시 Access Token을 발급받아 API 요청에 사용하는 것
- OAuth2PasswordRequestForm : 사용자의 username과 password를 폼 데이터로 받아오는 역할
- login_for_access_token : 사용자가 로그인 시 Access Token을 생성
- protected_route : 갖고 있는 Access Token을 사용해 API 접근이 가능한지 검증
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
app = FastAPI()
# OAuth2 토큰 URL 설정
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
# 가상의 사용자 데이터
fake_users_db = {
"testuser": {
"username": "testuser",
"hashed_password": get_password_hash("password123"),
}
}
@app.post("/token")
def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
user = fake_users_db.get(form_data.username)
if not user or not verify_password(form_data.password, user["hashed_password"]):
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid credentials")
access_token = create_access_token(data={"sub": form_data.username})
return {"access_token": access_token, "token_type": "bearer"}
@app.get("/protected")
def protected_route(token: str = Depends(oauth2_scheme)):
payload = verify_token(token)
if not payload:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")
return {"message": "Access granted", "user": payload["sub"]}
Reference
https://fastapi.tiangolo.com/ko/tutorial/security/simple-oauth2/#username-password_1
https://recording-it.tistory.com/19
https://databoom.tistory.com/entry/FastAPI-JWT-%EA%B8%B0%EB%B0%98-%EC%9D%B8%EC%A6%9D-6
'Web' 카테고리의 다른 글
[FastAPI] 클라우드 플랫폼, Qoddi를 활용한 FastAPI 배포 (0) | 2025.04.08 |
---|---|
[FastAPI] pytest의 개념과 활용 (0) | 2025.03.02 |
[FastAPI] DB Migration을 위한 alembic 개념과 활용 (0) | 2025.02.17 |
[FastAPI] 의존성 주입 (Dependency Injection) 개념과 활용 (0) | 2025.02.02 |
[FastAPI] 데이터베이스 Session 생성을 위한 get_db() 이해 (0) | 2025.02.01 |