FastAPI 서버개발, 사용자 인증

오늘날의 웹 애플리케이션은 사용자 인증이 필수적입니다. 사용자의 신원을 확인하고 보호하기 위해 다양한 인증 방법이 존재합니다. 본 포스트에서는 FastAPI를 사용하여 사용자 인증을 구현하는 방법에 대해 자세히 설명하겠습니다. FastAPI는 Python으로 쓰인 현대적인 웹 프레임워크로, 빠른 성능과 쉽게 사용할 수 있는 API 설계로 인기를 끌고 있습니다.

1. FastAPI 소개

FastAPI는 Python 3.6 이상에서 사용할 수 있으며, AsyncIO를 기반으로 비동기 프로그래밍을 지원합니다. 또한 자동 문서화 기능을 제공하여 Swagger UI 및 ReDoc을 통해 API 문서를 쉽게 생성할 수 있습니다. FastAPI는 Pydantic을 기반으로 데이터 검증 및 설정 관리를 효율적으로 처리합니다.

2. 사용자 인증의 중요성

사용자 인증은 사용자의 신원을 확인하고, 이를 기반으로 권한을 부여하는 과정입니다. 이는 다음과 같은 이유로 중요합니다:

  • 보안성: 사용자 데이터 보호 및 유출 방지
  • 접근 제어: 사용자에 따라 권한 부여 및 제한
  • 사용자 경험: 개인화된 서비스 제공

3. FastAPI에서의 사용자 인증 기법

FastAPI에서 사용자 인증을 구현할 수 있는 방법은 여러 가지가 있습니다. 가장 일반적인 방법은 JWT (JSON Web Token)를 사용하는 것입니다. JWT는 사용자를 인증하고 정보를 안전하게 전송할 수 있는 토큰 기반 인증 방법입니다.

4. 필요한 라이브러리 설치

FastAPI와 JWT를 사용하기 위해 필요한 라이브러리를 설치합니다. 아래 명령어를 사용하여 필요한 패키지를 설치하세요:

pip install fastapi uvicorn python-jose passlib[bcrypt]

5. 기본 FastAPI 설정

FastAPI 애플리케이션을 설정합니다. 다음 코드를 사용하여 기본 FastAPI 애플리케이션을 만드세요:

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def read_root():
    return {"Hello": "World"}

6. 사용자 모델 및 데이터베이스 설정

이제 사용자 모델을 정의하고 데이터베이스에 연결합니다. SQLAlchemy를 사용하여 데이터베이스와의 상호작용을 간단하게 처리할 수 있습니다. 아래는 기본적인 사용자 모델 코드입니다:

from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()

class User(Base):
    __tablename__ = "users"

    id = Column(Integer, primary_key=True, index=True)
    username = Column(String, unique=True, index=True)
    hashed_password = Column(String)

위 코드에서 사용자 모델을 정의했습니다. `username`과 `hashed_password`를 통해 기본적인 사용자 정보를 저장할 수 있습니다.

7. 비밀번호 해싱

사용자의 비밀번호를 안전하게 저장하기 위해 해싱을 사용합니다. Passlib 라이브러리를 사용하여 비밀번호를 해싱하고 검증할 수 있습니다:

from passlib.context import CryptContext

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

def get_password_hash(password):
    return pwd_context.hash(password)

def verify_password(plain_password, hashed_password):
    return pwd_context.verify(plain_password, hashed_password)

8. 사용자 추가 및 등록 엔드포인트

사용자를 등록할 수 있는 엔드포인트를 추가합니다. 사용자가 비밀번호를 안전하게 저장하도록 해싱 후 데이터베이스에 저장할 수 있습니다:

from fastapi import Depends, HTTPException
from sqlalchemy.orm import Session

Base.metadata.create_all(bind=engine)

# Create the user
@app.post("/users/")
def create_user(username: str, password: str, db: Session = Depends(get_db)):
    hashed_password = get_password_hash(password)
    db_user = User(username=username, hashed_password=hashed_password)
    db.add(db_user)
    db.commit()
    db.refresh(db_user)
    return db_user

9. JWT 토큰 생성

로그인 후 JWT 토큰을 생성하여 반환하는 엔드포인트를 추가합니다:

from datetime import datetime, timedelta
from jose import JWTError, jwt

SECRET_KEY = "your_secret_key"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

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

10. 로그인 엔드포인트

사용자가 로그인할 수 있도록 엔드포인트를 추가합니다.

@app.post("/token")
async def login(username: str, password: str, db: Session = Depends(get_db)):
    user = db.query(User).filter(User.username == username).first()
    if not user or not verify_password(password, user.hashed_password):
        raise HTTPException(status_code=400, detail="잘못된 사용자명 또는 비밀번호")
    access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    access_token = create_access_token(data={"sub": user.username}, expires_delta=access_token_expires)
    return {"access_token": access_token, "token_type": "bearer"}

11. 보호된 엔드포인트 설정

JWT 토큰으로 보호된 엔드포인트를 설정합니다. 사용자가 인증된 경우에만 접근할 수 있도록 구현합니다:

from fastapi import Security, Depends
from fastapi.security import OAuth2PasswordBearer

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

async def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)):
    credentials_exception = HTTPException(status_code=401, detail="유효하지 않은 자격 증명", headers={"WWW-Authenticate": "Bearer"})
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception
    except JWTError:
        raise credentials_exception
    user = db.query(User).filter(User.username == username).first()
    if user is None:
        raise credentials_exception
    return user

@app.get("/users/me")
async def read_users_me(current_user: User = Depends(get_current_user)):
    return current_user

12. FastAPI 서버 실행

모든 설정이 완료되었으니, 이제 FastAPI 서버를 실행할 준비가 되었습니다. 아래 명령어로 서버를 실행하세요:

uvicorn main:app --reload

위 명령어를 통해 FastAPI 서버가 `http://127.0.0.1:8000`에서 실행됩니다.

13. Swagger UI를 통한 API 문서화

FastAPI는 Swagger를 통해 자동으로 API 문서를 생성합니다. 브라우저에서 `http://127.0.0.1:8000/docs`로 이동하면 API 문서를 확인할 수 있습니다.

14. 결론

FastAPI를 사용한 사용자 인증 구현 방법에 대해 알아보았습니다. 비밀번호 해싱, JWT 기반 인증을 포함한 사용자 등록 및 로그인 기능을 제공하는 API를 구축했습니다. FastAPI의 강력한 기능을 통해 효율적으로 사용자 인증을 처리할 수 있습니다.

노트: 실제 애플리케이션에서는 비밀키와 같은 중요한 정보를 환경 변수에 저장하거나 더 안전한 방식으로 관리하는 것이 좋습니다.

15. 참고 자료