Skip to main content

Command Palette

Search for a command to run...

FastAPI를 활용한 인증 서버 구축 및 토큰 기반 인증 시스템 구현하기

Published
4 min readView as Markdown
FastAPI를 활용한 인증 서버 구축 및 토큰 기반 인증 시스템 구현하기

개요

오늘날 데이터 보안은 웹 서비스 개발에서 중요한 요소 중 하나입니다. FastAPI를 사용하여 안전한 인증 시스템을 구축하는 방법을 살펴보겠습니다. 이 글에서는 인증 서버의 개발, 리소스 서버에 토큰 인증 기능 추가, 그리고 클라이언트에서 이를 사용하는 방법까지 단계별로 설명합니다.

1. 인증 서버 개발

인증 서버란 무엇인가

인증 서버는 사용자의 자격 증명을 검증하고, 유효한 사용자에게 접근 토큰을 제공하는 서비스입니다. 이 토큰은 사용자가 다른 서비스(리소스 서버)에 접근할 수 있도록 하는 열쇠와 같은 역할을 합니다.

FastAPI와 JWT를 사용한 인증 서버 구축

FastAPI는 비동기 Python 웹 프레임워크로, 강력한 인증 및 보안 기능을 제공합니다. JWT (JSON Web Tokens)는 사용자 인증에 널리 사용되는 방식으로, 토큰 자체에 정보를 담고 있어 높은 효율성을 제공합니다.

주요 구현 단계:

  1. 환경 설정: 필요한 라이브러리 설치 (fastapi, uvicorn, pyjwt)

  2. JWT 토큰 생성 및 검증 로직 구현: 사용자의 자격 증명에 대한 검증 후 JWT 토큰 발급

  3. 사용자 인증 라우트 개발: 사용자가 자격 증명을 전송할 수 있는 엔드포인트 생성

코드 예시:

from datetime import datetime, timedelta
from typing import Optional
from passlib.context import CryptContext

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

from app.settings import SECRET_KEY, ALGORITHM, USERNAME, PASSWORD

ACCESS_TOKEN_EXPIRE_MINUTES = 60*24*7

# 비밀번호 해싱을 위한 설정
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

# 관리자 자격 증명 설정
admin_credentials = {
    USERNAME: pwd_context.hash(PASSWORD)
}

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


def create_access_token(data: dict, expires_delta: Optional[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


def verify_token(token: str, credentials_exception):
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        return payload
    except jwt.PyJWTError:
        raise credentials_exception


# 사용자 인증 함수
def authenticate_user(username: str, password: str):
    hashed_password = admin_credentials.get(username)
    if not hashed_password:
        return False
    if not pwd_context.verify(password, hashed_password):
        return False
    return True


@app.post("/token")
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
    user_authenticated = authenticate_user(form_data.username, form_data.password)
    if not user_authenticated:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Incorrect username or password",
            headers={"WWW-Authenticate": "Bearer"},
        )
    access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    access_token = create_access_token(
        data={"sub": form_data.username}, expires_delta=access_token_expires
    )
    return {"access_token": access_token, "token_type": "bearer"}


@app.get("/users/me")
async def read_users_me(token: str = Depends(oauth2_scheme)):
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )
    return verify_token(token, credentials_exception)

보안 고려사항

  • HTTPS 사용: 데이터 전송 시 암호화를 보장하기 위해 HTTPS 사용

  • 토큰 보안: 토큰의 유효기간 설정, 민감 정보는 토큰에 포함하지 않기

2. 리소스 서버에 토큰 인증 추가

리소스 서버의 역할

리소스 서버는 보호된 데이터와 기능을 호스팅하는 서버입니다. 사용자가 인증 서버로부터 발급받은 토큰을 사용하여 리소스 서버에 접근할 수 있습니다.

토큰 검증 로직 구현

리소스 서버는 들어오는 요청에 포함된 토큰을 검증하여, 유효한 요청에만 서비스를 제공합니다.

구현 방법:

  • FastAPI의 Depends 기능을 사용하여 API 엔드포인트에 토큰 검증 로직 적용

  • 토큰이 유효하지 않은 경우 접근 거부

코드 예시:

from fastapi import Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer
import jwt
from jwt import PyJWTError

from app.config.settings import SECRET_KEY, ALGORITHM

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")


def verify_token(token: str = Depends(oauth2_scheme)):
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        return payload
    except PyJWTError:
        raise HTTPException(
            status_code=401,
            detail="Invalid authentication credentials"
        )
@router.get("/dart/finance/business/{bizNum}", response_model=NewCompanyFinanceResponse)
async def get_company_finance_info(bizNum: str, token: str = Depends(verify_token)):
    """사업자등록번호로 기업 재무정보를 조회하는 함수
    Args:
        bizNum (str): 사업자등록번호
    Returns:
        NewCompanyFinanceResponse: 기업 재무정보
    """
    try:
        data = get_company_info(bizNum=bizNum)
        if not data:
            return NewCompanyFinanceResponse(newCompanyFinance=[])
        return NewCompanyFinanceResponse(newCompanyFinance=data)
    except Exception as e:
        err_msg = traceback.format_exc()
        logger.error(err_msg)
        raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))

보안 고려사항

  • 토큰 누출 방지: API 요청 시 토큰을 안전하게 전송

  • 적절한 권한 부여: 사용자에게 필요한 최소한의 권한만 부여

3. 클라이언트에서의 토큰 사용

클라이언트의 역할

클라이언트는 사용자가 인증 서버에 자격 증명을 제공하고, 발급받은 토큰을 사용하여 리소스 서버에 접근하는 인터페이스를 제공합니다.

토큰 발급 및 사용 방법

  1. 토큰 발급 요청: 클라이언트는 인증 서버에 사용자 이름과 비밀번호를 전송하여 토큰을 요청

  2. 토큰 저장 및 사용: 발급받은 토큰을 저장하고, 리소스 서버에 요청을 보낼 때 헤더에 포함

클라이언트 코드 예시 (Python, Java):

import requests

# 인증 서버에서 토큰 발급
auth_response = requests.post("http://호스트:포트/token", data={
    "username": "아이디",
    "password": "비밀번호"
})
token = auth_response.json()["access_token"]
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        HttpClient client = HttpClient.newHttpClient();
        String url = "http://호스트:포트/token";
        String formParams = "username=" + URLEncoder.encode("아이디", StandardCharsets.UTF_8) +
                            "&password=" + URLEncoder.encode("비밀번호", StandardCharsets.UTF_8);

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(url))
            .header("Content-Type", "application/x-www-form-urlencoded")
            .POST(HttpRequest.BodyPublishers.ofString(formParams))
            .build();

        try {
            HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
            System.out.println("Response status code: " + response.statusCode());
            System.out.println("Response body: " + response.body());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

보안 고려사항

  • 토큰 보안: 클라이언트 측에서 토큰을 안전하게 저장 및 관리

  • HTTPS 사용: 데이터 전송 시 암호화 보장


이 포스팅은 FastAPI를 활용한 인증 시스템의 구축과 클라이언트에서의 토큰 사용 방법을 자세하게 설명합니다. 보안을 최우선으로 고려하여 각 단계를 신중하게 설계하는 것이 중요합니다.

More from this blog

도메인 최적화 파인튜닝: Ai 기반 Toeic 문제 생성 모델 개발

1. 서론 프로젝트 배경 및 목적 이 프로젝트는 TOEIC 문제를 생성하는 AI 모델을 도메인 최적화 파인튜닝을 통해 개발하는 것입니다. 이 모델은 https://toeic4all.com 서비스에서 문제를 생성하는 데 사용됩니다. 사용한 기술 스택 Python PyTorch Hugging Face Transformers Unsloth Weights & Biases CUDA 12.1 2. 환경 설정 Unsloth 설치 방법 (CUD...

Jun 1, 20248 min read85
도메인 최적화 파인튜닝: Ai 기반 Toeic 문제 생성 모델 개발

쿠버네티스에서 PostgreSQL 데이터베이스 미러링 및 주기적 백업 설정

본문 1. 서론 프로젝트 개요 이번 프로젝트에서는 쿠버네티스 클러스터에서 PostgreSQL 데이터베이스를 미러링하고, 주기적으로 백업하는 방법을 설명합니다. 이를 통해 데이터의 신뢰성과 가용성을 높일 수 있습니다. 환경 설정 및 요구사항 쿠버네티스 클러스터 (노드 3대) PostgreSQL 14 백업 서버 (맥미니, Ubuntu Server 설치) Docker 및 Docker Compose 2. 준비 단계 쿠버네티스 클러스터 설정 ...

Jun 1, 20244 min read33
쿠버네티스에서 PostgreSQL 데이터베이스 미러링 및 주기적 백업 설정

쿠버네티스: 로드 밸런서와 노드포트 이해하기

쿠버네티스 환경에서 서비스에 외부 트래픽을 효과적으로 라우팅하는 것은 중요한 과제입니다. 이 글에서는 쿠버네티스의 두 가지 주요 서비스 유형인 로드 밸런서(LoadBalancer)와 노드포트(NodePort)에 대해 살펴보겠습니다. 노드포트(NodePort)란? 노드포트는 가장 기본적인 방법으로, 클러스터의 모든 노드에 동일한 포트를 열어 서비스에 접근할 수 있게 하는 방식입니다. 노드포트는 마치 집안에 있는 모든 문을 열어놓고, 집안에 있는 ...

Feb 18, 20242 min read19
R

Ruo’s Tech blog

14 posts