FastAPI 서버개발, Pydantic을 이용한 데이터 모델링

FastAPI는 현대적인 웹 API를 빠르게 구축할 수 있도록 도와주는 경량의 웹 프레임워크입니다. Python의 비동기 기능을 활용하여 매우 높은 성능을 제공하며, 개발자에게는 사용의 용이성과 명확한 문서화를 제공합니다. 본 글에서는 FastAPI 서버 개발과 Pydantic을 이용한 데이터 모델링에 대해 자세히 설명하겠습니다.

1. FastAPI란?

FastAPI는 Python 3.6 이상의 타입 힌트를 적극적으로 활용하여 API 문서를 자동으로 생성할 수 있는 프레임워크입니다. RESTful API, GraphQL API를 쉽게 만들 수 있으며, 비동기 처리를 지원하여 높은 성능을 제공합니다.

1.1 특징

  • 높은 성능: 스타트업과 규모가 큰 프로젝트에서 동시에 사용할 수 있을 정도로 빠릅니다.
  • 간편한 사용법: Flask와 유사한 방식으로 쉽게 배울 수 있습니다.
  • 자동 문서화: OpenAPI와 JSON Schema를 기반으로 API 문서가 자동으로 생성됩니다.
  • 타입 안전성: Pydantic과의 통합을 통해 데이터 검증과 변환이 매우 쉽고 안전합니다.

2. Pydantic이란?

Pydantic은 Python 데이터 검증 및 설정 관리 도구입니다. Python의 타입 힌트를 활용하여 입력된 데이터의 유효성을 검사하고, 데이터 변환 및 직렬화를 관리합니다. FastAPI는 Pydantic을 모델의 입력 및 출력을 다루는 데 활용합니다.

2.1 Pydantic의 주요 기능

  • 데이터 유효성 검사: 입력된 데이터가 지정된 형식에 맞는지 확인합니다.
  • 데이터 직렬화 및 역직렬화: 모델 인스턴스를 JSON과 같은 형식으로 쉽게 변환할 수 있습니다.
  • 에러 메시지: 데이터 검증 시 쉽게 이해할 수 있는 에러 메시지를 제공합니다.
  • 환경 변수 지원: .env 파일을 통해 환경 변수를 쉽게 로드할 수 있습니다.

3. FastAPI 및 Pydantic 설치하기

FastAPI와 Pydantic은 pip를 통해 쉽게 설치할 수 있습니다. 다음 명령어를 사용하여 설치합니다:

pip install fastapi uvicorn pydantic

여기서 uvicorn은 ASGI 서버로 FastAPI 애플리케이션을 실행하는 데 사용됩니다.

4. FastAPI 서버 개발하기

우선 FastAPI 애플리케이션을 생성한 후, Pydantic 모델을 사용하여 데이터 모델링을 시작합니다. 간단한 예제로 북(Book) API를 만들어보겠습니다.

4.1 FastAPI 애플리케이션 생성

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def root():
    return {"message": "Hello, FastAPI!"}

4.2 Pydantic 모델 생성

이제 API에 사용할 Pydantic 모델을 정의해 보겠습니다. 북(Book) 모델을 정의하려면 다음과 같이 작성할 수 있습니다:

from pydantic import BaseModel
from typing import Optional

class Book(BaseModel):
    id: int
    title: str
    author: str
    description: Optional[str] = None
    year: Optional[int] = None

4.3 API 엔드포인트 추가하기

이제 FastAPI 애플리케이션에 북 데이터를 추가하고 가져오는 엔드포인트를 추가합니다:

books = []

@app.post("/books/", response_model=Book)
async def create_book(book: Book):
    books.append(book)
    return book

@app.get("/books/{book_id}", response_model=Book)
async def read_book(book_id: int):
    for book in books:
        if book.id == book_id:
            return book
    return {"error": "Book not found"}

5. API 테스트하기

API를 테스트하기 위해서는 Uvicorn 서버를 실행하고, Postman이나 curl과 같은 도구를 사용할 수 있습니다. 아래 명령어로 서버를 실행합니다:

uvicorn main:app --reload

이제 브라우저에서 http://127.0.0.1:8000/docs에 접속하면 FastAPI가 자동으로 생성한 API 문서를 확인할 수 있습니다.

6. Pydantic의 고급 기능

Pydantic은 단순한 데이터 모델링 외에도 다음과 같은 여러 고급 기능을 제공합니다:

6.1 Validation (유효성 검사)

Pydantic은 유효성 검사기를 사용자 정의하여 제대로 사용하지 않은 형식이나 잘못된 값에 대해 쉽게 제어할 수 있습니다:

from pydantic import constr

class User(BaseModel):
    username: constr(min_length=4, max_length=20)
    email: EmailStr

    class Config:
        anystr_strip_whitespace = True

6.2 Nested Models (중첩 모델)

Pydantic 모델은 서로 중첩될 수 있어 복잡한 데이터 구조를 작업하는 데 매우 유용합니다:

class Publisher(BaseModel):
    name: str
    address: Optional[str] = None

class BookWithPublisher(BaseModel):
    title: str
    author: str
    publisher: Publisher

7. 결론

FastAPI와 Pydantic을 함께 사용하면 효율적이고 강력한 웹 API를 쉽게 구축할 수 있습니다. FastAPI의 비동기 처리 능력과 Pydantic의 데이터 유효성 검사는 개발 생산성을 극대화하는 데 주요한 요소입니다. 본 강좌에서는 간단한 예제를 통해 FastAPI와 Pydantic을 사용하여 데이터 모델링의 기초를 살펴봤습니다. 이를 바탕으로 더 복잡한 API를 설계하고 구현할 수 있습니다. FastAPI와 Pydantic을 통해 여러분의 프로젝트에 강력한 웹 API를 추가해보시기 바랍니다.

8. 참고 자료

FastAPI 서버개발, 경로 매개변수와 쿼리 매개변수

FastAPI는 Python으로 만든 웹 프레임워크로, 빠른 성능과 쉬운 사용성을 자랑합니다. 특히 RESTful API를 구축하는 데 강력한 도구를 제공합니다. 본 글에서는 FastAPI의 경로 매개변수와 쿼리 매개변수의 개념과 이들의 활용 방식에 대해 자세히 살펴보겠습니다.

1. FastAPI 개요

FastAPI는 스타트업부터 대규모 애플리케이션까지 다양한 환경에서 사용되는 현대적인 웹 프레임워크입니다. 이 프레임워크는 비동기 프로그래밍을 기본으로 하며, 자동 문서화 및 타입 검사를 지원합니다. 이러한 특징 덕분에 개발자는 효율적으로 API를 설계하고 구축할 수 있습니다.

1.1 FastAPI 설치하기

FastAPI를 사용하기 위해 먼저 FastAPI와 ASGI 서버(Uvicorn)를 설치해야 합니다. 아래의 명령어를 사용하여 설치할 수 있습니다.

pip install fastapi uvicorn

2. 경로 매개변수

경로 매개변수는 URL 경로의 일부로, 사용자가 요청하는 리소스를 동적으로 지정할 수 있게 해줍니다. URL 경로에서 변하는 부분을 변수처럼 설정하고, 이를 FastAPI 함수의 매개변수로 받아 처리할 수 있습니다.

2.1 경로 매개변수 사용 예제

다음은 경로 매개변수를 사용하는 간단한 FastAPI 애플리케이션의 예제입니다.

from fastapi import FastAPI

app = FastAPI()

@app.get("/items/{item_id}")
async def read_item(item_id: int):
    return {"item_id": item_id}

위의 예에서 @app.get("/items/{item_id}")는 URL 경로의 item_id 부분을 매개변수로 받아, 이를 read_item 함수에서 사용할 수 있도록 합니다. 클라이언트가 /items/42와 같은 요청을 보내면, item_id는 42로 설정되고, JSON 형식으로 응답됩니다.

2.2 여러 경로 매개변수 사용하기

FastAPI에서는 여러 개의 경로 매개변수를 동시에 사용할 수 있습니다. 예를 들어, 다음과 같은 코드를 작성할 수 있습니다.

@app.get("/users/{user_id}/items/{item_id}")
async def read_user_item(user_id: int, item_id: int):
    return {"user_id": user_id, "item_id": item_id}

위의 예제는 사용자의 ID와 항목의 ID를 동시에 받아서 응답합니다. 클라이언트가 /users/1/items/10와 같은 요청을 보낼 경우, user_iditem_id는 각각 1과 10으로 설정됩니다.

3. 쿼리 매개변수

쿼리 매개변수는 URL 경로 뒤에 추가되는 파라미터로, 클라이언트가 특정 데이터를 필터링하거나 페이지네이션할 때 종종 사용됩니다. URL의 물음표(?) 뒤에 설정되며, 여러 개의 매개변수를 “&”로 구분할 수 있습니다.

3.1 쿼리 매개변수 사용 예제

다음은 쿼리 매개변수를 사용하는 예제입니다.

@app.get("/items/")
async def read_items(skip: int = 0, limit: int = 10):
    return {"skip": skip, "limit": limit}

위 코드는 기본적으로 skiplimit 값을 받는 요청을 처리하며, 기본값은 각각 0과 10입니다. 클라이언트에서 /items/?skip=10&limit=5와 같은 요청을 보내면, skip은 10, limit은 5로 설정됩니다.

3.2 쿼리 매개변수와 경로 매개변수 혼용하기

FastAPI에서는 경로 매개변수와 쿼리 매개변수를 동시에 사용할 수 있습니다. 아래는 그 예제입니다.

@app.get("/users/{user_id}/items/")
async def read_user_items(user_id: int, skip: int = 0, limit: int = 10):
    return {"user_id": user_id, "skip": skip, "limit": limit}

이 예제에서는 사용자의 ID를 경로 매개변수로 받고, skiplimit를 쿼리 매개변수로 사용합니다. 클라이언트가 /users/1/items/?skip=5&limit=10와 같은 요청을 보내면, 서버는 해당 사용자 ID와 쿼리 매개변수 값을 기반으로 응답을 처리합니다.

4. 경로 및 쿼리 매개변수의 유효성 검사

FastAPI는 Pydantic을 기반으로 하여 경로 및 쿼리 매개변수에 대한 유효성 검사를 자동으로 수행합니다. 타입 힌트를 사용하여 데이터 타입을 지정하면, 잘못된 형식의 데이터가 들어오는 경우 자동으로 422 Unprocessable Entity 응답이 발생합니다.

4.1 유효성 검사 예제

다음은 유효성 검사를 포함한 간단한 예제입니다.

@app.get("/items/{item_id}")
async def read_item(item_id: int):
    if item_id <= 0:
        return {"error": "item_id는 1 이상의 정수여야 합니다."}
    return {"item_id": item_id}

위의 예제에서 item_id가 1보다 작거나 같을 경우 오류 메시지를 반환하며, 클라이언트에서는 항상 올바른 입력값을 제공해야 합니다.

5. 결론

FastAPI는 경로 매개변수와 쿼리 매개변수를 통해 RESTful API를 더욱 유연하고 편리하게 설계할 수 있는 강력한 기능을 제공합니다. 본 글에서 설명한 내용을 토대로 FastAPI를 활용하여 다양한 API 엔드포인트를 구축하고, 데이터 필터링 및 요청 처리를 보다 효율적으로 처리할 수 있습니다.

이제 여러분은 FastAPI에서 경로 매개변수와 쿼리 매개변수의 사용법을 익혔습니다. 이 기능을 바탕으로 더 복잡한 애플리케이션을 개발해 보시기 바랍니다. FastAPI의 높은 성능과 사용성을 경험하면서, 여러분의 프로젝트에 맞는 최적의 RESTful API를 구현해 보세요.

이 글이 FastAPI 서버 개발에 대한 실질적인 도움이 되었기를 바랍니다. 궁금한 점이나 추가적인 질문이 있다면 댓글로 남겨주십시오.

FastAPI 서버개발, FastAPI 애플리케이션 구조화

FastAPI는 현대적이고, 빠르며, 비동기적이며, 쉬운 RESTful API를 만드는 데 사용할 수 있는 훌륭한 프레임워크입니다. FastAPI를 사용하여 서버를 개발할 때, 애플리케이션 구조화는 확장성, 유지 보수성 및 팀 작업을 고려할 때 매우 중요한 요소입니다. 이 글에서는 FastAPI 애플리케이션을 구조화하는 방법에 대해 자세히 탐구해 보겠습니다.

1. FastAPI 소개

FastAPI는 Python 3.7 이상에서 사용할 수 있는 웹 프레임워크입니다. Starlette과 Pydantic을 기반으로 하여 빠른 속도와 높은 성능을 자랑합니다. FastAPI는 데이터 검증, 순서 매기기, 문서화 등의 기능을 내장하고 있어 개발자가 API를 더 쉽게 만들 수 있도록 도와줍니다. 또한, OpenAPI 스펙을 자동으로 생성하여 API 문서화를 빠르고 쉽게 할 수 있습니다.

FastAPI의 주요 특징

  • 비동기 지원: FastAPI는 비동기 프로그래밍을 지원하여 높은 성능을 제공합니다.
  • 자동화된 데이터 검증: Pydantic을 사용하여 입력 데이터를 자동으로 검증합니다.
  • 자동 생성된 API 문서: Swagger UI 및 ReDoc을 사용하여 API 문서를 자동으로 생성합니다.
  • 사용의 용이성: 개발자가 최소한의 코드로 API를 개발할 수 있도록 설계되었습니다.

2. FastAPI 애플리케이션 구조화의 필요성

대규모 애플리케이션에서 코드의 유지 보수성 및 확장성을 높이기 위해서는 명확한 구조화가 필수적입니다. FastAPI 애플리케이션을 구조화하는 방법은 다음과 같습니다:

  • 모듈화: 관련 성격을 가진 구성 요소들을 모아서 모듈 단위로 관리합니다.
  • 디렉토리 구조: 명확한 디렉토리 구조를 설계하여 코드의 가독성을 높입니다.
  • 프레임워크 활용: FastAPI의 내장 기능을 최대한 활용하여 반복된 코드를 줄입니다.

2.1 모듈화

모듈화를 통해 애플리케이션의 특정 기능이나 역할을 별도의 파일이나 디렉토리로 나누어 관리할 수 있습니다. 예를 들어, 사용자 인증, 데이터베이스 접근, API 라우팅 등을 각각의 모듈로 나누어 관리합니다.

2.2 디렉토리 구조

애플리케이션의 디렉토리 구조는 다음과 같은 형태로 구성할 수 있습니다:


my_fastapi_app/
├── app/
│   ├── main.py
│   ├── models/
│   │   └── user.py
│   ├── schemas/
│   │   └── user.py
│   ├── routes/
│   │   └── user.py
│   ├── database/
│   │   └── db.py
│   └── services/
│       └── user.py
└── requirements.txt

위의 디렉토리 구조는 FastAPI 애플리케이션의 기본적인 구조를 보여줍니다. 각 디렉토리의 역할은 다음과 같습니다:

  • main.py: FastAPI 애플리케이션의 진입점입니다. 모든 라우터를 포함하고, 데이터베이스 연결 등의 초기 설정을 수행합니다.
  • models: 데이터베이스 모델을 정의하는 곳입니다.
  • schemas: 데이터 검증 및 직렬화를 위한 Pydantic 모델을 정의합니다.
  • routes: API 엔드포인트를 설정합니다.
  • database: 데이터베이스 연결 및 세션 관리 관련 코드를 포함합니다.
  • services: 비즈니스 로직을 구현한 것입니다.

3. FastAPI 애플리케이션 구축 예제

이제 FastAPI 애플리케이션을 실제로 구축하는 과정을 단계별로 살펴보겠습니다.

3.1 환경 설정

먼저 FastAPI와 데이터베이스 클라이언트인 SQLAlchemy를 설치합니다. requirements.txt 파일에 다음과 같은 내용을 작성합니다:


fastapi
uvicorn
pydantic
sqlalchemy
databases

이제 다음 명령어를 통해 패키지를 설치합니다:

pip install -r requirements.txt

3.2 데이터베이스 설정

PostgreSQL 데이터베이스를 사용할 경우, 먼저 .env 파일을 만들어 환경 변수를 설정해야 합니다. .env 파일의 내용은 다음과 같습니다:


DATABASE_URL=postgresql://username:password@localhost/dbname

database/db.py 파일을 작성하여 데이터베이스 연결을 설정합니다:


from sqlalchemy import create_engine, MetaData
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
import databases
import os

DATABASE_URL = os.getenv('DATABASE_URL')

database = databases.Database(DATABASE_URL)
metadata = MetaData()
engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

Base = declarative_base()

3.3 모델 만들기

models/user.py 파일에서 User 모델을 정의합니다:


from sqlalchemy import Column, Integer, String
from ..database.db import Base

class User(Base):
    __tablename__ = 'users'

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

3.4 스키마 만들기

schemas/user.py 파일에서 Pydantic 모델을 정의합니다:


from pydantic import BaseModel

class UserBase(BaseModel):
    username: str
    email: str

class UserCreate(UserBase):
    password: str

class User(UserBase):
    id: int

    class Config:
        orm_mode = True

3.5 라우터 만들기

routes/user.py 파일에서 라우터를 정의합니다:


from fastapi import APIRouter, Depends
from ..models.user import User
from ..schemas.user import UserCreate, User
from ..database.db import SessionLocal

router = APIRouter()

@router.post("/users/", response_model=User)
async def create_user(user: UserCreate):
    async with SessionLocal() as db:
        db_user = User(username=user.username, email=user.email)
        db.add(db_user)
        await db.commit()
        await db.refresh(db_user)
        return db_user

3.6 메인 애플리케이션 설정

app/main.py 파일에서 FastAPI 애플리케이션을 구성합니다:


from fastapi import FastAPI
from .database.db import database
from .routes import user

app = FastAPI()

app.include_router(user.router)

@app.on_event("startup")
async def startup():
    await database.connect()

@app.on_event("shutdown")
async def shutdown():
    await database.disconnect()

3.7 서버 실행

이제 Uvicorn를 사용하여 서버를 실행할 준비가 되었습니다. 다음 명령어로 서버를 실행합니다:

uvicorn app.main:app --reload

3.8 Swagger UI로 테스트하기

서버가 실행되면 http://localhost:8000/docs에서 Swagger UI를 통해 API를 테스트할 수 있습니다. 이곳에서 생성한 API 엔드포인트를 자동으로 확인하고, 요청을 수행할 수 있습니다.

4. 결론

FastAPI는 현대적인 API를 개발하는 데 매우 유용한 프레임워크입니다. 애플리케이션을 구조화하는 것은 코드의 가독성, 유지 보수성 및 확장성을 높이는 데 중요한 역할을 합니다. 위에서 설명한 방법으로 FastAPI 애플리케이션의 구조를 설계하고 구현하면, 더욱 체계적이고 효율적인 백엔드 서버를 개발할 수 있습니다.

FastAPI의 다양한 기능을 최대한 활용하고, 모듈화 및 디렉토리 구조를 잘 설계하여 안정적이고 확장 가능한 애플리케이션을 구축해 보세요. 추가적인 조언과 도움을 원하신다면 FastAPI 문서 및 커뮤니티를 참고하시기 바랍니다.

FastAPI 서버개발, Flask와 비교

웹 애플리케이션 개발에 있어서 프레임워크는 매우 중요한 역할을 합니다. Python 생태계에서 많이 사용되는 두 가지 프레임워크인 Flask와 FastAPI에 대해 비교하고, FastAPI의 장점을 강조하면서 서버 개발의 기초를 다루어 보겠습니다.

1. 개요

웹 프레임워크는 클라이언트의 요청을 처리하고, 데이터를 처리하여 클라이언트에게 응답하는 역할을 합니다. Flask는 경량화된 마이크로 프레임워크로, 간단한 웹 애플리케이션을 만드는 데 특히 유용합니다. 반면, FastAPI는 비동기적 기능과 자동 API 문서화를 지원하여 현대적인 웹 서버 개발에 적합합니다.

2. Flask 소개

Flask는 2010년 Armin Ronacher에 의해 만들어진 마이크로 웹 프레임워크입니다. Flask는 간결하고 유연한 구성을 특징으로 하여, 개발자가 원하는 대로 프레임워크를 확장할 수 있습니다.

2.1. Flask의 특징

  • 가벼운 구조: 최소한의 코드베이스로 확장 가능
  • 유연성: 다양한 플러그인과 확장 기능을 지원
  • Jinja2 템플릿 엔진: HTML 템플릿을 쉽게 작성할 수 있도록 지원
  • 직관적인 라우팅 시스템

2.2. Flask 예제 코드

from flask import Flask, jsonify, request

app = Flask(__name__)

@app.route('/api/hello', methods=['GET'])
def hello_world():
    return jsonify(message='Hello, World!')

@app.route('/api/greet', methods=['POST'])
def greet_user():
    data = request.get_json()
    name = data.get('name', 'Guest')
    return jsonify(message=f'Hello, {name}!')

if __name__ == '__main__':
    app.run(debug=True)

3. FastAPI 소개

FastAPI는 2018년 Sebastián Ramírez에 의해 만들어진 웹 프레임워크로, Pydantic과 Starlette를 기반으로 하고 있습니다. FastAPI는 비동기 프로그래밍을 지원하고, 타입 힌팅을 통해 데이터 검증 및 자동 문서화를 제공하는 점에서 현대적인 애플리케이션 개발에 적합합니다.

3.1. FastAPI의 특징

  • 비동기 지원: async 및 await를 통해 고성능 비동기 애플리케이션 개발 가능
  • 자동 API 문서화: OpenAPI와 Swagger UI를 통해 API 문서를 자동 생성
  • 타입 검증: Python 3.6 이상의 타입 힌트를 사용하여 데이터 유효성 검증
  • 우수한 성능: Starlette를 기반으로 하여 경량화 및 빠른 속도 제공

3.2. FastAPI 예제 코드

from fastapi import FastAPI

app = FastAPI()

@app.get('/api/hello')
async def hello_world():
    return {'message': 'Hello, World!'}

@app.post('/api/greet')
async def greet_user(name: str = 'Guest'):
    return {'message': f'Hello, {name}!'}

4. Flask와 FastAPI 비교

특징 Flask FastAPI
비동기 지원 비동기 작업을 지원하지만 기본적으로 동기 방식 비동기 방식 기본 지원
자동 문서화 문서화 도구 필요 (예: Flask-RESTPlus) 자동 생성 (OpenAPI, Swagger UI)
타입 힌팅 있으나 필수는 아님 강력한 타입 힌팅 지원
성능 모든 작업을 동기적으로 수행 비동기 처리로 높은 성능 제공

5. FastAPI의 장점

FastAPI는 특히 API 서버 개발과 관련하여 여러 가지 장점을 제공합니다.

5.1. 성능

FastAPI는 Starlette를 기반으로 하여 비동기 처리를 통해 높은 성능을 발휘합니다. 이를 통해 적은 리소스로도 수많은 요청을 처리할 수 있습니다.

5.2. 개발 생산성

자동 문서화 및 타입 힌팅을 통한 데이터 검증 덕분에 코드 작성이 간편해지고, 유지 보수성도 높아집니다. 또한, 클라이언트와의 소통을 원활하게 해주는 API 문서가 자동으로 생성되므로 개발자와 비개발자 간의 협업이 용이해집니다.

5.3. 유연한 데이터 처리

Pydantic을 활용하여 데이터 검증을 강력하고 유연하게 처리할 수 있습니다. 유효하지 않은 데이터는 자동으로 오류 메시지를 반환하므로, 클라이언트 측에서 보다 명확한 피드백을 받을 수 있습니다.

6. 결론

FastAPI는 높은 성능과 개발 생산성을 제공하는 현대적인 웹 프레임워크입니다. Flask와 같은 전통적인 프레임워크와 비교할 때 비동기 처리와 자동 문서화, 강력한 타입 힌팅 등에서 많은 장점을 가지고 있습니다. 이러한 특성 덕분에 FastAPI는 RESTful API 서버 개발에 매우 적합하다고 할 수 있습니다. 기존의 Flask 개발자도 FastAPI의 장점을 활용하여 더 나은 웹 서비스를 구축할 수 있을 것입니다.

7. 참고 자료

FastAPI 서버개발, OAuth2 및 JWT(JSON Web Token) 인증 구현

1. 개요

FastAPI는 Python으로 작성된 최신 웹 프레임워크로, 매우 빠른 성능과 현대적인 웹 개발의 다양한 요구를 충족하기 위한 강력한 기능들을 제공합니다. 본 문서에서는 FastAPI를 사용하여 OAuth2 및 JSON Web Token(JWT) 인증 방식을 구현하는 방법을 소개하고, 이를 통해 안전한 API를 설계하는 방법을 살펴보겠습니다.

2. 요구 사항

본 강좌를 진행하기 위해 필요한 사항은 다음과 같습니다:

  • Python 3.6 이상이 설치된 환경
  • FastAPI 라이브러리 및 관련 패키지
  • Uvicorn: ASGI 서버
  • Pydantic: 데이터 검증 및 설정 관리 라이브러리

3. FastAPI 및 필요한 패키지 설치

콘솔에서 아래의 명령어를 실행하여 FastAPI와 필요한 패키지를 설치합니다:

pip install fastapi uvicorn python-jose passlib[bcrypt]

4. FastAPI 서버 설정

FastAPI 서버를 설정하려면, 간단한 FastAPI 애플리케이션을 만들어 보겠습니다. 다음 코드를 사용하여 기본적인 FastAPI 애플리케이션을 만들어 보세요:

from fastapi import FastAPI

app = FastAPI()

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

이 코드를 main.py라는 파일로 저장한 후, 다음 명령어로 서버를 실행합니다:

uvicorn main:app --reload

이제 http://127.0.0.1:8000로 접속하면 “Hello World” 메시지를 확인할 수 있습니다.

5. OAuth2 및 JWT 인증 방식

OAuth2는 인증 및 권한 부여를 위한 프로토콜로, 사용자가 리소스에 접근할 수 있도록 권한을 부여하는 방식입니다. JWT는 세션 정보를 저장할 수 있는 JSON 객체로, 다양한 정보를 담을 수 있으며, 서명을 통해 데이터의 무결성을 확인할 수 있는 장점이 있습니다.

6. 사용자 인증 및 JWT 생성

회원 가입 및 로그인을 구현할 필요가 있습니다. 다음 예제는 사용자 정보를 저장하기 위한 Pydantic 모델과 JWT 생성을 위한 방법을 보여줍니다:

from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import JWTError, jwt
from datetime import datetime, timedelta
from pydantic import BaseModel
from passlib.context import CryptContext

# 비밀 키와 알고리즘 설정
SECRET_KEY = "your-secret-key"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

# 비밀번호 암호화 설정
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

# 사용자 모델
class User(BaseModel):
    username: str
    email: str | None = None
    full_name: str | None = None
    disabled: bool | None = None

# 로그인 사용자 모델
class UserInDB(User):
    hashed_password: str

# OAuth2PasswordBearer 인스턴스 생성
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

# 사용자 데이터 저장소 (예: 데이터베이스 대신 사용)
fake_users_db = {
    "johndoe": {
        "username": "johndoe",
        "full_name": "John Doe",
        "email": "johndoe@example.com",
        "hashed_password": pwd_context.hash("password"),
        "disabled": False,
    }
}

# 비밀번호 검증 함수
def verify_password(plain_password, hashed_password):
    return pwd_context.verify(plain_password, hashed_password)

# 사용자 조회 함수
def get_user(db, username: str):
    if username in db:
        user_dict = db[username]
        return UserInDB(**user_dict)

# JWT 생성 함수
def create_access_token(data: dict, expires_delta: timedelta | None = 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

# FastAPI 앱 설정
app = FastAPI()

@app.post("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
    user = get_user(fake_users_db, 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="잘못된 사용자명 또는 비밀번호",
            headers={"WWW-Authenticate": "Bearer"},
        )
    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"}

위의 코드를 통해 로그인 엔드포인트가 생성되었습니다. 사용자는 사용자명을 입력하고 비밀번호를 제공하여 JWT를 받을 수 있습니다.

7. JWT 인증이 필요한 엔드포인트

인증이 필요한 API 엔드포인트를 만들기 위해 FastAPI의 종속성을 활용할 수 있습니다. 다음은 JWT가 필요한 API 엔드포인트를 만드는 방법입니다:

from fastapi import Security

def get_current_user(token: str = Depends(oauth2_scheme)):
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        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
        token_data = User(username=username)
    except JWTError:
        raise credentials_exception
    user = get_user(fake_users_db, username=token_data.username)
    if user is None:
        raise credentials_exception
    return user

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

위의 코드에서는 /users/me 엔드포인트를 만들어 권한이 있는 경우에만 사용자가 본인의 정보를 조회할 수 있도록 설정했습니다.

8. 전체 코드

이제까지 구현한 전체 코드를 정리하면 다음과 같습니다:

from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import JWTError, jwt
from datetime import datetime, timedelta
from pydantic import BaseModel
from passlib.context import CryptContext

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

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

class User(BaseModel):
    username: str
    email: str | None = None
    full_name: str | None = None
    disabled: bool | None = None

class UserInDB(User):
    hashed_password: str

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

fake_users_db = {
    "johndoe": {
        "username": "johndoe",
        "full_name": "John Doe",
        "email": "johndoe@example.com",
        "hashed_password": pwd_context.hash("password"),
        "disabled": False,
    }
}

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

def get_user(db, username: str):
    if username in db:
        user_dict = db[username]
        return UserInDB(**user_dict)

def create_access_token(data: dict, expires_delta: timedelta | None = 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

app = FastAPI()

@app.post("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
    user = get_user(fake_users_db, 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="잘못된 사용자명 또는 비밀번호",
            headers={"WWW-Authenticate": "Bearer"},
        )
    
    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"}

def get_current_user(token: str = Depends(oauth2_scheme)):
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        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
        token_data = User(username=username)
    except JWTError:
        raise credentials_exception
    user = get_user(fake_users_db, username=token_data.username)
    if user is None:
        raise credentials_exception
    return user

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

9. 마무리

이제 FastAPI를 사용하여 OAuth2와 JWT를 활용한 인증 기능을 구현하는 방법을 알아보았습니다. 이 예제를 바탕으로 실제 애플리케이션에 필요한 인증 및 권한 부여 로직을 추가하여, 보다 안전한 API를 설계할 수 있습니다. FastAPI의 뛰어난 성능과 간편한 사용법 덕분에 웹 서비스 개발이 훨씬 수월해질 것입니다.

이 글이 FastAPI와 OAuth2, JWT 인증을 이해하는 데 도움이 되었기를 바랍니다. 더 나아가 FastAPI의 다양한 기능을 탐색하시기를 권장합니다.