출처: https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/
아래의 내용은 공식 사이트의 내용을 제 경험과 생각을 추가하여 다시 정리한 것 입니다.
로그인(인증)하여 토큰 받기
- 이전의 코드(FastAPI - 15 (보안1 - QAuth2, Bearer))를 이용하여 추가 합니다.
- OAuth2PasswordRequestForm를 이용 하여 username과 password 가져옵니다.
- 전달 된 사용자 정보로 인증을 진행 합니다.
- 인증이 완료되면 토큰을 생성하여 반환 하고 그렇지 않으면 HTTPException을 발생 시킵니다.
코드
from typing import Union
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from pydantic import BaseModel
from typing_extensions import Annotated
# 가짜 사용자 정보를 정의 합니다. 실 업무에서는 DB에서 가져오는 경우가 많습니다.
# johndoe는 활성화 상태이고, alice는 비활성화 상태입니다.
fake_users_db = {
"johndoe": {
"username": "johndoe",
"full_name": "John Doe",
"email": "johndoe@example.com",
"hashed_password": "fakehashedsecret",
"disabled": False,
},
"alice": {
"username": "alice",
"full_name": "Alice Wonderson",
"email": "alice@example.com",
"hashed_password": "fakehashedsecret2",
"disabled": True,
},
}
app = FastAPI()
# 패스워드를 해싱하는 함수(실제로는 해싱하지 않습니다.)
def fake_hash_password(password: str):
return "fakehashed" + password
# 클라이언트의 토큰을 받는 OAuth2PasswordBearer 인스턴스 생성
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
# 사용자 정보를 담는 pydantic의 BaseModel을 상속하는 User 클래스 생성
class User(BaseModel):
username: str
email: Union[str, None] = None
full_name: Union[str, None] = None
disabled: Union[bool, None] = None
# User 클래스를 상속하여 해싱된 패스워드를 추가
class UserInDB(User):
hashed_password: str
# 전달된 db에서 사용자 정보를 가져오는 함수
def get_user(db, username: str):
if username in db:
user_dict = db[username]
return UserInDB(**user_dict)
# 토큰을 디코딩하는 함수
def fake_decode_token(token):
# 지금은 보안이 전혀 적용 되지 않았습니다.
user = get_user(fake_users_db, token)
return user
# 토큰을 디코딩하는 함수를 의존성 주입으로 사용
# 현재 사용자를 가져오는 함수
async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
user = fake_decode_token(token)
# 만약 사용자가 없다면, HTTPException을 발생시킵니다.
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials",
headers={"WWW-Authenticate": "Bearer"},
)
return user
# get_current_user 함수를 의존성 주입으로 사용
# 현재 활성화 된 사용자를 가져오는 함수
async def get_current_active_user(
current_user: Annotated[User, Depends(get_current_user)]
):
# 현재 사용자가 비활성화 상태라면, HTTPException을 발생시킵니다.
if current_user.disabled:
raise HTTPException(status_code=400, detail="Inactive user")
return current_user
# 토큰을 생성하는 함수(토큰을 생성하기 위해 인증 - 로그인 하는 함수)
@app.post("/token")
# OAuth2PasswordRequestForm을 의존성 주입으로 사용
# OAuth2PasswordRequestForm은 username과 password(Form Data)를 가져와서 form_data에 저장합니다.
async def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]):
# 클라이언트로 부터 전달된 username을 가진 사용자 정보를 가져옵니다.
user_dict = fake_users_db.get(form_data.username)
# 만약 사용자 정보가 없다면, HTTPException을 발생시킵니다.
if not user_dict:
raise HTTPException(status_code=400, detail="Incorrect username or password")
# 그렇지 않으면 UserInDB 클래스의 인스턴스를 생성합니다.
user = UserInDB(**user_dict)
# 클라이언트로 부터 전달된 패스워드를 해싱합니다.
hashed_password = fake_hash_password(form_data.password)
# 해싱된 패스워드가 사용자 정보의 해싱된 패스워드와 일치하지 않는다면, HTTPException을 발생시킵니다.
if not hashed_password == user.hashed_password:
raise HTTPException(status_code=400, detail="Incorrect username or password")
# 인증이 완료되었으므로, 토큰을 생성하여 반환합니다.
# 토큰은 username을 반환하고 타입은 bearer입니다.
return {"access_token": user.username, "token_type": "bearer"}
# 현재 유저(user)를 반환하는 함수
@app.get("/users/me")
# get_current_active_user 함수를 의존성 주입으로 사용
async def read_users_me(
current_user: Annotated[User, Depends(get_current_active_user)]
):
return current_user
테스트
위의 코드를 실행 하고 fastapi의 docs를 확인 합니다.
http://localhost:8000/docs
로그인(인증 절차)하지 않고 /users/me
실행
먼저 로그인(인증 절차)을 하지 않고 /users/me
를 실행 해 보겠습니다.
예상대로 {"detail":"Not authenticated"}
가 출력됩니다.
로그인(인증 절차 - /token)하여 토큰 받기
로그인을 위해 /token
을 실행 해 보겠습니다.
username과 password를 입력하고 Execute
를 클릭 합니다.
인터넷상의 로그인 화면이라고 생각하시면 됩니다.
사용자 정보는 위의 fake_users_db
를 참조 하시면 됩니다.johndoe
, secret
을 입력하고 Execute
를 클릭 합니다.
비밀번호는 해싱되어 코드에 저장되어 있습니다. 평문은 secret
입니다.
⓵ 로그인(인증 절차)가 완료되어 토큰이 생성 되고 json 형태로 반환됩니다.
위의 api는 토큰이 생성되어 반환 되는 것을 확인하기 위한 api 입니다.
클라이언트에 토큰을 저장하기능이 구현 되어 있지 않아/user/me
에 접속 하면 {"detail":"Not authenticated"}
가 출력됩니다.
'Authorization'를 이용하여 로그인 하기
다시 fastapi의 docs로 돌아갑니다.Authorization
를 이용하여 로그인을 하겠습니다.
docs의 우측 상단의 Authorize
를 클릭 하여 새창이 뜨면 username
과 password
를 입력합니다.joehndoe
, secret
을 입력합니다.
아래의 Authorize
를 클릭 합니다.
성공적으로 로그인이 되었습니다.
우측의 colse
버턴을 클릭 합니다.(Logout 하지 않습니다.)/users/me
를 실행 해 보겠습니다.
Curl
를 보겠습니다.
curl -X 'GET' \
'http://localhost:8000/users/me' \
-H 'accept: application/json' \
-H 'Authorization: Bearer johndoe'
Head 정보에 Bearer 토큰이 포함되어 있습니다.
access_token에는 johndoe가 포함되어 있습니다.
Response body
를 보겠습니다.
{
"username": "johndoe",
"email": "johndoe@example.com",
"full_name": "John Doe",
"disabled": false,
"hashed_password": "fakehashedsecret"
}
johndoe
의 정보가 return 되었습니다.
'Authorization' 로그아웃 하기
Authorize
를 클릭 하고 Logout
을 클릭 합니다.
로그인 폼 화면이 다시 나타나면 colse
를 클릭 하여 창을 닫습니다./users/me
를 실행 해 보겠습니다.
로그아웃 되었으므로 {"detail":"Not authenticated"}
가 출력됩니다.
'웹 프레임워크 > FastAPI' 카테고리의 다른 글
FastAPI - 18 (보안4 - QAuth2, Bearer, JWT - 클라이언트 구성 ) (0) | 2023.12.24 |
---|---|
FastAPI - 17 (보안3 - JWT) (0) | 2023.12.24 |
FastAPI - 15 (보안1 - QAuth2, Bearer) (0) | 2023.12.24 |
FastAPI - 14 (의존성 주입) (0) | 2023.12.19 |
FastAPI - 13 (JSON 호환 인코더 / 본문 업데이트) (0) | 2023.12.19 |