💾 Backend/FastAPI

Fast API에서 비밀번호 해싱하기

hjinn0813 2024. 11. 8. 16:28
728x90

사수님의 제안으로, 지난 시간까지 나 혼자 풀스택으로 CRUD가 되는 게시판을 만들었다. 기능적인 부분을 우선적으로 생각해서 만들었는데, 이후에 내가 까먹고 구현하지 않은 필수 기능이 있다는걸 깨달았다.

내가 쓴 글을 수정/삭제하려면 본인임을 인증할 수 있는 장치가 필요하다는 것!😅

그래서 CRUD 기능이 완성된 게시판에 비밀번호 입력 기능을 추가했다. 비밀번호 기능을 추가한 후에, 몇 가지의 에러가 돌아가면서 발생하고 있어서 아직 끝나지 않았지만, 최초 글 생성 시에는 제대로 작동하고 있는걸 확인했기 때문에 기록해본다.✍

※ CRUD가 되는 게시판 만들기, 백엔드 코드 설명 - https://hjinn0813.tistory.com/149

 

DB에 CRUD가 되는 게시판 만들기 - Backend

나중에 풀스택으로 성장하고 싶다고 말했지만, 이렇게 빨리 백엔드를 배우게 될 줄은 몰랐다.😂

hjinn0813.tistory.com


의존성 설치

pip install passlib[bcrypt]

FastAPI에서 유저의 비밀번호를 안전하게 해싱하고 검증하려면 위 명령어로 passlib, bcrypt 알고리즘을 설치해야 한다.


모델 클래스 정의

from sqlalchemy import Column, Integer, String, Text, DateTime
from sqlalchemy.ext.declarative import declarative_base
from pydantic import BaseModel, validator

Base = declarative_base()

class Board(Base):
  __tablename__ = "board"
  id = Column(Integer, primary_key=True, index=True)
  name = Column(String, nullable=False)
  password = Column(String, nullable=False)
  date = Column(DateTime, nullable=False)
  title = Column(String, nullable=False)
  content = Column(Text, nullable=False)

# 글 생성 모델
class BoardCreate(BaseModel):
  name: str 
  password: str
  title: str 
  content: str

  @validator('password')
  def validate_password(cls, v):
    if not (8 <= len(v) <= 12):
      raise HTTPException(status_code=400, detail="Password must be between 8 and 12 characters")
    if not any(char.isdigit() for char in v):
      raise HTTPException(status_code=400, detail="Password must contain at least one digit")
    return v
  • 가장 먼저 model.py 파일에 모델 클래스를 정의했다. 코드에 대한 자세한 설명은 위에 링크된 이전 게시글에 있으니 생략하고, 여기서 '비밀번호'라는 속성을 추가하면서 달라진 부분만 언급해보겠다.
  • 우선 Pydantic에서 validator를 가져왔다. validator는 Pydantic에서 특정 필드의 값을 검사할때 쓰는 데코레이터이다.
  • 비밀번호 속성이 추가되었으니, 기본 모델 클래스와 글 생성 모델에 password라는 속성을 추가했다. 클라이언트가 비밀번호를 str 타입으로 보내주기 때문에 이렇게 작성한다. 또한 글 생성 모델에는 @validator로 비밀번호의 조건을 작성했는데, 프론트엔드(React)에서 비밀번호의 길이와 유형을 if문으로 검증하고 있기 때문에, 백엔드 코드에도 작성했다.

CRUD 함수 정의

다음으로는 crud.py 파일에 있는 '게시글 생성' 함수에 비밀번호를 해싱하는 코드를 작성했다.

클라이언트에서 유저가 입력한 문자열 비밀번호는 서버에 전달되면서 해싱된 값으로 저장된다. 나중에 유저가 로그인 등 비밀번호를 다시 입력하면, 서버는 클라이언트에서 유저가 입력한 문자열 비번과 DB에 저장된 해시값을 비교해서, 실제 그 사람이 맞는지 확인한다.

from sqlalchemy.orm import Session
from .board_model import BoardCreate
from .board_db import get_db
from datetime import datetime
from fastapi import HTTPException
from passlib.context import CryptContext

# 비밀번호 검증을 위한 CryptContext 객체 생성
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

# POST: 게시글 생성
def create_post(db: Session, board: BoardCreate):
  if not board.password:
    raise HTTPException(status_code=400, detail="Password is required")

  hashed_password = pwd_context.hash(board.password) # 비밀번호 해싱

  new_board = Board(
    name=board.name,
    password=hashed_password,  # 해싱된 비밀번호 저장
    title=board.title,
    content=board.content,
    date=datetime.now()
  )
  
  db.add(new_board)
  db.commit()
  db.refresh(new_board)
  return new_board
  • from passlib.context import CryptContext
    → 앞서 pip로 설치한 passlib을 통해 CryptContext를 불러왔다.
    CryptContext는 비밀번호를 안전하게 해싱하고 검증하기 위한 설정관리자이다. 어떤 해싱 알고리즘을 사용할지 결정해주고, 해당 알고리즘으로 비밀번호를 암호화하거나 본인이 맞는지 확인하는 역할을 한다.
  • pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
    → pwd_context 라는 변수에 CryptContext 객체를 생성했다. 비번 검증을 위해 어떤 알고리즘을 사용할지 결정했다는 의미. 여기서는 bcrypt 알고리즘을 사용하겠다고 지정했고, deprecated="auto"는 오래된 해싱 방식을 자동으로 무시하겠다는 설정이다.
  • create_post 함수 내부에서, raise HTTPException
    → 최초 글 생성 시에 유저가 비밀번호를 입력하지 않으면 오류를 발생시킨다. 비밀번호 없이 게시글을 생성하는 것을 방지하기 위한 유효성 검사.
  • pwd_context.hash()
    → 앞에 CryptContext 객체를 담은 pwd_context 변수를 가져와서, 유저가 입력한 board.password를 해싱하여 hashed_password에 저장한다.
  • new_board 정의
    → Board 라는 모델 클래스의 구조에 맞춰서 새로운 게시글 객체 new_board를 생성하고, 생성된 게시들의 데이터는 board.title, board.content처럼 저장된다. 하지만 비번은 hashed_password(해싱된 값)이 저장된다.

엔드포인트 정의

# 글 생성
@router.post("/board")
def create_post_endpoint(board: BoardCreate, db: Session = Depends(get_db)):
    if not board.password:
        raise HTTPException(status_code=400, detail="Password is required")
    return create_post(db, board)
  • 그리고 마지막으로 api.py 파일에서 엔드포인트를 정의한다.
  • board는 클라이언트에서 보낸 데이터로 BoardCreate 모델 사용하고, 만약에 입력창에 비번이 입력되지 않았으면 HTTPException으로 오류 출력. 그 이외의 경우에는 create_post 함수 불러와서 글 생성하여 DB에 저장한다.

이렇게 코드들을 작성하고 http://localhost:8001/docs 로 가서 Swagger UI로 테스트를 해봤다.

request에서 문자열로 입력된 비밀번호가 response에서 해시값으로 나오는걸 확인했다!🙌

김연경 선수 팬입니다..🏐


이렇게 또 하나의 기능을 배우면서, 백엔드는 정말 어렵다는 생각을 다시 한번 했다. 물론 결국 해결하면 '이렇게 되는거구나'를 깨달으며 희열을 느끼기는 하지만..😅

그리고 작년 이맘때의 나는 이제 막 기초 코딩을 배우기 시작한 왕초보였는데, 일년 사이에 주니어 개발자로 백엔드 코드까지 보고 있게 될 줄은 몰랐다. 인생은 앞으로가 어떻게 될지 모르기 때문에 더 흥미로운 것 같다. 이제는 나의 미래를 어떻게 그려나갈지 대략적인 청사진이 있으니까, 개발자로 잘 성장해보자!🌱

728x90