FastAPI에서 세션 관리와 로그인, 로그아웃 기능은 주로 쿠키JWT(JSON Web Tokens) 또는 OAuth2를 사용하여 구현할 수 있습니다. 세션 관리는 서버에 상태를 저장하는 전통적인 방식과, 상태를 저장하지 않고 토큰을 사용하는 방법으로 나뉩니다. 여기서는 JWT 기반의 로그인/로그아웃세션 기반의 로그인/로그아웃 방법을 설명하겠습니다.

1. FastAPI에서 JWT 기반 로그인/로그아웃 구현

JWT는 서버에 세션 정보를 저장하지 않고도 인증할 수 있는 방식을 제공하며, 각 요청마다 JWT를 사용하여 인증합니다. 이를 통해 애플리케이션은 무상태(stateless)로 유지될 수 있습니다.

JWT 설정 및 구현

필요한 라이브러리 설치

FastAPI와 JWT를 사용하기 위해 필요한 라이브러리를 설치합니다.

pip install fastapi[all] pyjwt passlib

JWT 토큰 생성 및 검증을 위한 설정

FastAPI에서 JWT를 생성하고 검증하는 예제를 살펴보겠습니다.

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

# 앱 생성
app = FastAPI()

# 보안 설정
SECRET_KEY = "mysecretkey"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

# 유저 데이터 예시
fake_users_db = {
    "testuser": {
        "username": "testuser",
        "full_name": "Test User",
        "email": "test@example.com",
        "hashed_password": "$2b$12$KIXdFh6fDF/hjEPGzTj6me2VVd./tFMyOP58/GKse4Gzi8TOSwlxu", # "password"의 해시값
    }
}

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

# OAuth2를 사용한 비밀번호 기반 인증
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

# 유저 모델 정의
class User(BaseModel):
    username: str
    email: Optional[str] = None
    full_name: Optional[str] = None

class UserInDB(User):
    hashed_password: str

class Token(BaseModel):
    access_token: str
    token_type: str

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)

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

def authenticate_user(fake_db, username: str, password: str):
    user = get_user(fake_db, username)
    if not user or not verify_password(password, user.hashed_password):
        return False
    return user

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

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

@app.get("/users/me", response_model=User)
async def read_users_me(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=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials")
        token_data = {"username": username}
    except jwt.PyJWTError:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials")

    user = get_user(fake_users_db, username=token_data["username"])
    if user is None:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="User not found")
    return user

코드 설명

  • login 엔드포인트는 사용자의 자격 증명을 검증하고 JWT를 발급합니다.
  • read_users_me 엔드포인트는 발급된 JWT를 통해 사용자를 인증합니다. JWT가 유효하지 않으면 401 Unauthorized 응답을 반환합니다.
  • JWT 토큰은 sub 필드에 사용자의 이름을 포함하고, 만료 시간을 설정할 수 있습니다.

실행 및 테스트

서버 실행 후 /token 엔드포인트에 POST 요청을 보내어 access_token을 받습니다. 이 토큰을 /users/me 엔드포인트의 Authorization 헤더에 Bearer 타입으로 포함하여 요청하면 사용자 정보를 확인할 수 있습니다.


2. FastAPI에서 쿠키 기반 세션 관리 및 로그인/로그아웃 구현

세션 기반 인증은 보통 쿠키를 사용하여 세션 정보를 관리하며, 사용자 정보를 서버에 저장하여 관리합니다. 다음은 간단한 세션 기반 로그인/로그아웃 예제입니다.

FastAPI에서 세션 관리 예제

세션 관리를 위해 starlette.middleware.sessions를 사용하여 세션 미들웨어를 추가합니다.

필요한 라이브러리 설치

pip install fastapi[all]

세션을 이용한 로그인 구현

from fastapi import FastAPI, Request, Depends, Form, HTTPException
from fastapi.responses import RedirectResponse
from fastapi.middleware.sessions import SessionMiddleware
from starlette.responses import JSONResponse

# FastAPI 앱 생성 및 세션 미들웨어 추가
app = FastAPI()
app.add_middleware(SessionMiddleware, secret_key="mysecretkey")

# 유저 데이터베이스 예시
fake_users_db = {
    "testuser": {
        "username": "testuser",
        "password": "password"
    }
}

def authenticate_user(username: str, password: str):
    user = fake_users_db.get(username)
    return user and user["password"] == password

@app.post("/login")
async def login(request: Request, username: str = Form(...), password: str = Form(...)):
    if authenticate_user(username, password):
        request.session["username"] = username
        return JSONResponse(content={"message": "Login successful"})
    else:
        raise HTTPException(status_code=400, detail="Invalid credentials")

@app.get("/logout")
async def logout(request: Request):
    request.session.clear()
    return JSONResponse(content={"message": "Logout successful"})

@app.get("/profile")
async def profile(request: Request):
    username = request.session.get("username")
    if not username:
        raise HTTPException(status_code=401, detail="Not authenticated")
    return {"username": username}

코드 설명

  • SessionMiddleware: 쿠키 기반 세션을 위해 SessionMiddleware를 FastAPI 애플리케이션에 추가합니다.
  • login 엔드포인트: 로그인 자격을 확인하고, 세션에 사용자 정보를 저장합니다.
  • logout 엔드포인트: 세션을 삭제하여 사용자를 로그아웃합니다.
  • profile 엔드포인트: 세션에 저장된 사용자 정보를 확인합니다. 인증되지 않은 사용자는 401 Unauthorized 응답을 받습니다.

실행 및 테스트

로그인 후 /profile 엔드포인트를 호출하면 세션에 저장된 사용자 정보를 확인할 수 있습니다. 로그아웃 요청 후 /profile을 다시 호출하면 인증되지 않은 상태로 간주됩니다.

결론

FastAPI에서 JWT와 세션 기반 로그인/로그아웃을 설정하는 방법을 살펴보았습니다. FastAPI는 다양한 인증 방식을 지원하며, RESTful API 및 인증 시스템을 효율적으로 구현할 수 있습니다.

파이썬 Sanic 프레임워크란?

Sanic은 파이썬으로 작성된 비동기 웹 프레임워크로, 고성능비동기 처리에 최적화되어 있습니다. 특히 비동기/병렬 처리를 지원하므로, 네트워크 요청이 많은 실시간 API, 웹소켓, 비동기 작업 처리 등의 작업에 매우 적합합니다. Python의 asyncio를 사용하여 비동기 I/O 작업을 수행하며, 대규모의 비동기 API 또는 서비스에 적합합니다.

Sanic의 가장 큰 특징은 비동기적으로 작성된다는 점입니다. 이를 통해 네트워크 요청을 비동기적으로 처리하며, 성능을 최대로 끌어올릴 수 있습니다.

주요 특징

  1. 비동기 처리: async/await 구문을 통해 비동기 처리와 병렬 작업이 가능합니다.
  2. 고성능: 요청-응답 처리가 매우 빠르며, 초당 수천 개의 요청을 처리할 수 있습니다.
  3. WSGI 미지원: Sanic은 WSGI를 사용하지 않으며, 직접 이벤트 루프를 관리합니다.
  4. 웹소켓 지원: 웹소켓을 쉽게 사용할 수 있으며, 실시간 통신에 적합합니다.
  5. 자동화된 JSON 응답: 편리한 JSON 처리 기능을 제공합니다.

Sanic 설치

Sanic은 pip를 사용하여 쉽게 설치할 수 있습니다.

pip install sanic

1. 기본 Sanic 예제

Sanic의 가장 간단한 "Hello World" 예제입니다.

from sanic import Sanic
from sanic.response import json

# 애플리케이션 인스턴스 생성
app = Sanic("HelloWorldApp")

# GET 요청에 대한 라우팅 설정
@app.route("/")
async def hello_world(request):
    return json({"message": "Hello, world!"})

# 서버 실행
if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8000)

설명

  • Sanic("HelloWorldApp"): Sanic 애플리케이션을 생성합니다. Sanic의 애플리케이션은 비동기적으로 동작하므로, 함수에 async 키워드를 사용합니다.
  • @app.route("/"): 라우팅을 설정하여, / 경로에 대한 GET 요청을 처리합니다.
  • return json(...): JSON 응답을 반환합니다. Sanic은 JSON 응답을 매우 쉽게 처리할 수 있습니다.
  • app.run(): 서버를 실행하며, 기본적으로 0.0.0.0에서 8000번 포트로 실행됩니다.

2. 비동기 작업 처리 예제

Sanic의 비동기 기능을 활용하면 비동기 I/O 작업을 매우 간단하게 처리할 수 있습니다. 예를 들어, 비동기적으로 데이터를 가져오고 처리하는 API를 만들어보겠습니다.

from sanic import Sanic
from sanic.response import json
import asyncio

app = Sanic("AsyncApp")

# 비동기 GET 요청 처리
@app.route("/async")
async def async_example(request):
    await asyncio.sleep(1)  # 1초 대기
    return json({"message": "This was an async request!"})

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8000)

설명

  • await asyncio.sleep(1): 비동기적으로 1초 대기합니다. 다른 작업을 블로킹하지 않고 대기 상태를 유지할 수 있습니다.
  • 비동기 작업을 통해 I/O 바운드 작업을 효율적으로 처리할 수 있습니다.

3. POST 요청 처리 예제

Sanic을 사용하여 POST 요청을 처리하고, 클라이언트로부터 데이터를 받아 처리할 수 있습니다.

from sanic import Sanic
from sanic.response import json

app = Sanic("PostExampleApp")

# POST 요청 처리
@app.route("/post", methods=["POST"])
async def handle_post(request):
    data = request.json  # 요청의 JSON 데이터 가져오기
    return json({"received_data": data})

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8000)

설명

  • request.json: 클라이언트가 보낸 POST 요청에서 JSON 데이터를 가져옵니다.
  • POST 요청을 처리할 때는 methods=["POST"]를 통해 HTTP 메서드를 지정합니다.

4. URL 매개변수 처리 예제

Sanic은 URL에서 매개변수를 쉽게 받아 처리할 수 있습니다.

from sanic import Sanic
from sanic.response import json

app = Sanic("ParamsExampleApp")

# URL 매개변수 처리
@app.route("/hello/<name>")
async def greet_user(request, name):
    return json({"message": f"Hello, {name}!"})

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8000)

설명

  • @app.route("/hello/<name>"): URL에서 <name>이라는 매개변수를 받아서 해당 값을 처리합니다.
  • greet_user(request, name): 함수의 두 번째 인자로 URL에서 추출된 name 값을 받습니다.

5. 미들웨어 사용 예제

Sanic에서 미들웨어를 사용하여 요청이나 응답 전에 공통 작업을 처리할 수 있습니다.

from sanic import Sanic
from sanic.response import json

app = Sanic("MiddlewareExampleApp")

# 요청 전 미들웨어
@app.middleware("request")
async def add_request_header(request):
    request.headers["X-Custom-Header"] = "CustomValue"

# 응답 전 미들웨어
@app.middleware("response")
async def add_response_header(request, response):
    response.headers["X-Processed-Time"] = "Processed in Sanic"

# 기본 GET 요청 처리
@app.route("/")
async def index(request):
    return json({"message": "Check the headers!"})

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8000)

설명

  • @app.middleware("request"): 요청이 들어오기 전에 실행되는 미들웨어를 정의합니다. 예제에서는 요청 헤더에 X-Custom-Header를 추가합니다.
  • @app.middleware("response"): 응답을 반환하기 전에 실행되는 미들웨어를 정의합니다. 응답 헤더에 X-Processed-Time 값을 추가합니다.

6. Sanic의 웹소켓 지원 예제

Sanic은 웹소켓을 지원하여 실시간 통신을 구현할 수 있습니다. 다음 예제는 간단한 웹소켓 서버입니다.

from sanic import Sanic
from sanic.response import json
from sanic.websocket import WebSocketProtocol

app = Sanic("WebSocketExampleApp")

# 웹소켓 연결 처리
@app.websocket("/ws")
async def websocket_handler(request, ws):
    while True:
        data = await ws.recv()  # 클라이언트로부터 메시지 수신
        await ws.send(f"Echo: {data}")  # 받은 메시지를 그대로 다시 보냄

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8000, protocol=WebSocketProtocol)

설명

  • @app.websocket("/ws"): 웹소켓 경로를 정의합니다.
  • ws.recv(): 클라이언트로부터 메시지를 수신합니다.
  • ws.send(): 수신된 메시지를 클라이언트로 다시 전송합니다(에코 서버).

7. 파일 업로드 처리 예제

Sanic에서 파일 업로드를 처리하는 방법입니다.

from sanic import Sanic
from sanic.response import json

app = Sanic("FileUploadExampleApp")

@app.route("/upload", methods=["POST"])
async def upload_file(request):
    file = request.files.get('file')  # 업로드된 파일 가져오기
    file_content = file.body  # 파일의 내용
    file_name = file.name  # 파일명

    return json({"file_name": file_name, "file_size": len(file_content)})

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8000)

설명

  • request.files.get('file'): 업로드된 파일을 가져옵니다.
  • file.body: 파일의 내용을 가져옵니다.
  • 업로드된 파일의 이름과 크기를 JSON으로 반환합니다.

결론

Sanic은 비동기 I/O 처리와 고성능 웹 애플리케이션을 구축하는 데 매우 적합한 프레임워크입니다. 비동기 처리, 웹소켓 지원, 미들웨어와 같은 강력한 기능을 제공하여 실시간 처리나 대규모 네트워크 요청이 필요한 환경에서 뛰어난 성능을 발휘합니다.

더 복잡한 응용 프로그램이나 다른 기능에 대해 궁금한 점이 있다면 언제든지 질문해 주세요!

파이썬 Bottle 프레임워크란?

Bottle은 파이썬에서 사용되는 경량 마이크로 웹 프레임워크로, 간결하고 빠른 웹 애플리케이션 개발에 중점을 둡니다. 모든 것이 하나의 파일에 들어갈 수 있으며, 외부 종속성 없이 간단한 프로젝트에 적합합니다. 특히, RESTful API, 웹 서비스 또는 소형 웹 애플리케이션을 신속하게 구축할 때 유용합니다.

주요 특징

  • 파일 하나에 모든 것: Bottle 애플리케이션은 하나의 파일에 담길 수 있어 간단하고 관리하기 쉽습니다.
  • 종속성 없음: Bottle은 자체적으로 동작하며, 추가적인 라이브러리가 필요하지 않습니다.
  • 간편한 URL 라우팅: 요청 경로(URL)와 해당 경로에 대한 처리를 쉽게 매핑할 수 있습니다.
  • 빠른 템플릿 엔진 내장: 내장된 템플릿 엔진을 사용하여 HTML 페이지를 쉽게 렌더링할 수 있습니다.
  • 내장 서버 지원: 기본 HTTP 서버를 내장하고 있어 별도의 웹 서버 설정 없이 바로 실행 가능합니다.

설치 방법

Bottle을 설치하려면 pip을 사용하여 간단히 설치할 수 있습니다.

pip install bottle

1. 기본 Bottle 예제

Bottle의 가장 간단한 예제는 Hello World 애플리케이션입니다. 이는 웹 브라우저에서 URL을 입력하면 "Hello World"라는 메시지를 반환하는 서버입니다.

from bottle import route, run

# 기본 경로에 대한 처리
@route('/')
def hello():
    return "Hello World!"

# 서버 실행
run(host='localhost', port=8080)

설명

  • @route('/'): 경로('/')에 대해 요청이 들어오면 hello() 함수를 호출하여 응답합니다.
  • run(host='localhost', port=8080): 애플리케이션을 로컬에서 실행하고, 브라우저에서 http://localhost:8080으로 접속하면 응답을 확인할 수 있습니다.

2. URL 라우팅 예제

Bottle의 라우팅 시스템을 사용하면 다양한 URL을 처리할 수 있습니다.

from bottle import route, run

# 사용자 이름을 URL 경로로 받아서 출력
@route('/hello/<name>')
def greet(name):
    return f"Hello, {name}!"

run(host='localhost', port=8080)

설명

  • @route('/hello/<name>'): 경로 /hello/ 뒤에 사용자가 입력한 이름을 받습니다. 예를 들어, http://localhost:8080/hello/Alice로 접속하면 "Hello, Alice!"가 출력됩니다.

3. GET 및 POST 요청 처리 예제

Bottle을 사용하여 GET 및 POST 요청을 처리할 수 있습니다. HTML 폼을 통해 POST 요청을 보내고, 서버에서 해당 데이터를 처리하는 예제입니다.

from bottle import route, run, template, request

# GET 요청 시 폼을 보여줌
@route('/login')
def login_form():
    return '''
        <form action="/login" method="post">
            Username: <input name="username" type="text" />
            Password: <input name="password" type="password" />
            <input value="Login" type="submit" />
        </form>
    '''

# POST 요청 처리
@route('/login', method='POST')
def login_submit():
    username = request.forms.get('username')
    password = request.forms.get('password')

    if username == "admin" and password == "secret":
        return f"Welcome, {username}!"
    else:
        return "Login failed."

run(host='localhost', port=8080)

설명

  • /login 경로로 GET 요청이 들어오면 로그인 폼을 보여줍니다.
  • 폼을 제출하면 POST 요청으로 사용자 이름과 비밀번호가 전송되고, 서버는 이를 처리하여 로그인 결과를 반환합니다.

4. 템플릿 사용 예제

Bottle은 내장된 템플릿 엔진을 사용하여 HTML을 동적으로 생성할 수 있습니다. 다음은 템플릿을 사용하여 사용자 이름을 동적으로 HTML 페이지에 표시하는 예제입니다.

from bottle import route, run, template

# 템플릿을 사용한 응답
@route('/hello/<name>')
def greet(name):
    return template('<b>Hello {{name}}</b>!', name=name)

run(host='localhost', port=8080)

설명

  • template() 함수는 템플릿을 처리하고 동적 HTML을 반환합니다. 템플릿 안에서 {{name}}은 Python에서 전달된 변수 name으로 대체됩니다.

5. 정적 파일 제공 예제

Bottle을 사용하여 정적 파일(이미지, CSS, JavaScript 등)을 제공할 수도 있습니다.

from bottle import route, run, static_file

# 정적 파일을 제공하는 경로 설정
@route('/static/<filename>')
def serve_static(filename):
    return static_file(filename, root='./static')

run(host='localhost', port=8080)

설명

  • static_file() 함수는 지정된 경로에서 파일을 찾아 반환합니다. root='./static'은 파일을 ./static 디렉터리에서 찾도록 설정한 것입니다.
  • 브라우저에서 http://localhost:8080/static/example.png과 같이 정적 파일을 요청할 수 있습니다.

6. Bottle + JSON API 예제

RESTful API를 만들 때도 Bottle은 매우 유용합니다. 다음은 JSON 데이터를 반환하는 API 예제입니다.

from bottle import route, run, response
import json

# JSON 데이터를 반환하는 경로
@route('/api/data')
def api_data():
    response.content_type = 'application/json'
    return json.dumps({'name': 'Alice', 'age': 30})

run(host='localhost', port=8080)

설명

  • /api/data 경로는 JSON 형식으로 데이터를 반환합니다. response.content_typeapplication/json으로 설정하여 클라이언트가 반환되는 데이터를 JSON으로 인식하게 합니다.
  • 브라우저나 API 클라이언트에서 이 경로에 요청하면 JSON 응답을 받을 수 있습니다.

결론

Bottle은 파이썬에서 간단한 웹 애플리케이션을 구축할 때 적합한 프레임워크로, 설치와 사용이 매우 간단하면서도 필요한 대부분의 기능을 제공합니다. URL 라우팅, GET/POST 요청 처리, 템플릿 시스템, 정적 파일 제공, JSON API 생성 등 다양한 기능을 통해 웹 애플리케이션 개발을 빠르고 간결하게 할 수 있습니다.

더 복잡한 기능이나 확장된 애플리케이션이 필요하면 말씀해 주세요!

+ Recent posts