현재 사용자 가져오기

현재 사용자 가져오기 (Get Current User)

직전 장에서는 보안 시스템(의존성 주입 시스템에 기반한)이 _경로 동작 함수_에 tokenstr로 넘겨줬어요. 그런데 토큰 문자열 하나만 받아서는 실제로 쓸모가 없죠. 이제 그 토큰으로 로그인한 사용자를 찾아내는 법을 배워요.

출처: 공식문서

직전 장에서 봤던 코드는 이런 모양이었어요.

from typing import Annotated

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

app = FastAPI()

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

@app.get("/items/")
async def read_items(token: Annotated[str, Depends(oauth2_scheme)]):
    return {"token": token}

사실 이건 "토큰을 받았지만 그게 누구의 것인지는 전혀 모르는" 상태예요. 우리가 원하는 건 토큰의 주인, 즉 현재 사용자를 얻는 거예요.

참고

Annotated를 쓰고 싶지 않다면 token: str = Depends(oauth2_scheme)처럼 기본값으로도 같은 동작을 해요. 다만 가능하면 Annotated 버전을 쓰는 게 권장돼요.

사용자 모델 만들기

먼저 Pydantic 사용자 모델을 만들어요. 본문을 선언할 때 Pydantic을 쓰듯, 다른 어디서든 같은 방식으로 쓸 수 있어요.

from typing import Annotated

from fastapi import Depends, FastAPI
from fastapi.security import OAuth2PasswordBearer
from pydantic import BaseModel

app = FastAPI()

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

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

def fake_decode_token(token):
    return User(
        username=token + "fakedecoded", email="[email protected]", full_name="John Doe"
    )

async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
    user = fake_decode_token(token)
    return user

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

User 모델은 idusername처럼 인증에 필요한 최소 필드를 선언하고, 나머지는 선택적으로 만들어요. 이제 이 모델을 의존성에서 돌려주겠죠.

get_current_user 의존성 만들기

의존성도 하위 의존성(sub-dependency)을 가질 수 있다는 걸 기억하나요? get_current_user는 앞서 만든 oauth2_scheme을 하위 의존성으로 갖는 의존성이에요.

즉, 직전에 _경로 동작_에서 직접 token을 받던 것과 똑같이, 이제는 get_current_user라는 의존성이 oauth2_scheme 하위 의존성으로부터 str 형태의 token을 받는 구조예요.

async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
    user = fake_decode_token(token)
    return user

Depends(oauth2_scheme)가 토큰을 받아 오면, 이 의존성은 그 토큰을 fake_decode_token에 넘겨 사용자를 만들어 돌려줘요.

사용자 가져오기

fake_decode_token은 (가짜) 유틸리티 함수로, 토큰을 str로 받아 우리의 Pydantic User 모델을 돌려줘요. 실제 앱에서는 이 자리에 데이터베이스에서 토큰 주인을 찾는 로직이 들어가겠지만, 개념은 같아요.

def fake_decode_token(token):
    return User(
        username=token + "fakedecoded", email="[email protected]", full_name="John Doe"
    )

현재 사용자 주입하기

이제 _경로 동작_에서 get_current_userDepends로 사용해서, current_user 파라미터에 사용자가 주입되도록 해요. 여기서도 파라미터의 타입을 Pydantic 모델 User로 선언하기 때문에, 함수 안에서 자동 완성과 타입 검사의 도움을 그대로 받을 수 있어요. current_user.username 같은 접근이 에디터에서 바로 잡혀요.

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

"본문도 Pydantic 모델로 선언하는데, 혹시 헷갈리지 않을까?" 싶을 수 있어요. FastAPI는 여기서 Depends를 쓰고 있다는 걸 아니까 전혀 혼동하지 않아요. Depends(get_current_user)에서 나온 값은 의존성의 결과로 취급되고, 요청 본문으로 취급되지 않거든요.

다른 모델도 쓸 수 있어요

이제 Depends를 이용해 보안 메커니즘을 의존성 주입 수준에서 처리하면서, 경로 동작 함수 안에서는 그냥 현재 사용자를 바로 받아 쓰면 돼요.

그런데 흥미로운 점은 여기서 어떤 모델이나 데이터를 쓰든 상관없다는 거예요. User라는 Pydantic 모델을 쓸 필요조차 없어요.

  • idemail만 있고 username이 없는 모델을 쓰고 싶어요? 물론 가능해요.
  • 그냥 str 하나만 돌려받고 싶어요? 됩니다.
  • dict로 받을까요? 됩니다.
  • 데이터베이스 클래스 모델 인스턴스를 그대로 돌려줄까요? 이 역시 똑같이 동작해요.
  • 로그인하는 게 사람이 아니라 봇이나 외부 시스템이라 토큰만 있으면 되나요? 그것도 똑같아요.

이 의존성 시스템은 같은 타입(User)을 돌려주는 여러 의존성을 두는 걸 허용하도록 설계돼 있어요. "한 가지 타입의 데이터를 돌려줄 수 있는 의존성은 하나뿐"이라는 제약이 없다는 뜻이에요. 응답에 어떤 형태든, 상황에 맞는 모델을 자유롭게 골라 쓸 수 있죠.

요약

이제 경로 동작 함수 안에서 현재 사용자를 바로 받아 쓸 수 있게 됐어요. 목표까지 반쯤 온 셈이에요. 아직 남은 건, 실제로 사용자가 usernamepassword를 보내는 _경로 동작_을 추가하는 일이에요. 그건 다음 장에서 다룰게요.

더 알아보기 (Learn more)