Programming/Project Log

[가계부 만들기] Backend - 회원 관리 기능 #2

minarae7 2023. 3. 29. 23:07
728x90
반응형

이제 여기서는 회원 정보를 처리하는 방법에 대해서 정리할 것이다.

암호화 또는 인증에 관련된 내용은 두 가지 정도로 정리할 수 있을 것이다.

첫 번째로 회원의 암호를 저장할 때 일반 텍스트로 저장하는 것이 아니고 hash로 처리한 후에 저장하고 로그인할 때 이렇게 hash 처리된 값과 입력한 암호가 일치하는지 확인하는 기능이 필요하다.

두 번째, 인증이 성공한 이후에 해당 사용자는 인증된 사용자임을 파악하기 위해서 토큰을 발생하고 이 토큰이 있는 요청에 대해서만 처리해줄 것이다.

이를 위해서 별도의 파일로 분리하여서 관련 코드를 정의하도록 하겠다.

인증 

인증 관련 내용에 대한 파일은 app 디렉토리 밑에 libraries 디렉토리를 생성하고 auth.py 파일을 생성하여 해당 파일에서 정리하도록 할 것이다.

디렉토리 및 파일 생성

이제 인증에서 사용할 파이썬 패키지들을 설치해보도록 할 것이다.

$ poetry add passlib bcrypt "python-jose[cryptography]"
or
$ pip install passlib bcrypt "python-jose[cryptography]"

원래는 poetry add를 하는 것만으로 패키지 설치가 되어야 하지만 이것만 해서 설치되지 않는 경우가 있어서 추가로 pip install을 해야하는 경우가 있을 수 있다. poetry로 설치가 안되면 pip install로 설치하면 된다.

코드를 작성해보도록 하겠다.

반응형

패스워드 암호화

먼저 사용자가 입력한 패스워드를 hash 처리해서 저장하고 로그인을 시도할 때 이렇게 hash 처리된 값이 올바른 값인지 확인하는 절차를 진행하도록 하겠다.

from passlib.context import CryptContext

# 암호화를 위한 객체 생성
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

# 패스워드를 암호화하는 함수
def get_password_hash(password: str) -> str:
    return pwd_context.hash(password)

# 암호화된 패스워드와 입력된 패스워드를 비교하는 함수
def verify_password(plain_password: str, hashed_password: str) -> bool:
    return pwd_context.verify(plain_password, hashed_password)

패스워드를 암호화하는 함수와 암호화된 패스워드와 사용자가 입력한 패스워드가 일치하는 비교하는 함수를 작성하였다.

이제 로그인하기 위한 코드를 작성해보도록 하겠다.

728x90

JWT

JWT는 Json Web Token의 약자로 근래에 많이 사용하는 토큰 방식이다. 자세한 내용은 공식 사이트에서 확인할 수 있으며 잘 정리된 사이트를 첨부한다.

 

JWT.IO

JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.

jwt.io

 

 

JWT(Json Web Token) 알아가기

jwt가 생겨난 이유부터 jwt의 실제 구조까지 | 사실 꾸준히 작성하고 싶었던 글이지만 JWT를 제대로 개념을 정리하고 구현을 진행해본 적이 없었는데 리얼월드 프로젝트를 진행하면서 JWT에 대한

brunch.co.kr

사용자가 로그인에 성공하면 사용자에게 사용자 정보와 앞으로 요청에서 사용할 토큰을 발급한다.

auth.py에서는 사용자 정보를 기반으로 토큰을 생성하는 코드와 사용자에게 받은 토큰이 유효한지 확인하는 코드를 추가한다.

from datetime import datetime, timedelta
from typing import Optional

from jose import JWTError, jwt
from fastapi import HTTPException, status
from fastapi.security import OAuth2PasswordBearer

# 임의로 사용할 비밀키(secret key)
SECRET_KEY = "7395e755307a3276d60f253cc58ecd3b80ab82d95fdaeb30a424c8f52b9faa5c"
ALGORITHM = "HS256"

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="login")


# JWT 생성 함수
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


# JWT 검증 함수
def decode_access_token(token: str):
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        return payload
    except JWTError:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid authentication credentials",
            headers={"WWW-Authenticate": "Bearer"},
        )

관련 코드에서 크게 어려운 부분은 없다. jwt 패키지를 통해서 encode하고 decode하는 방식이기 때문에 어려운 코드는 아니다.

전달된 토큰에서 인증이 실패하면 Exception을 발생시켜서 사용할 수 없는 토큰이라는 것을 전달하면 된다.

토큰을 생성할 때는 토큰의 유효시간을 설정하는데 시간이 전달되면 전달된 시간으로 설정하고 값이 없다면 15분 동안만 유효하도록 설정하였다.

해당 파일의 전체 코드는 아래와 같이 구성하였다.

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

from jose import JWTError, jwt
from fastapi import HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm


# 암호화를 위한 객체 생성
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

# 임의로 사용할 비밀키(secret key)
SECRET_KEY = "7395e755307a3276d60f253cc58ecd3b80ab82d95fdaeb30a424c8f52b9faa5c"
ALGORITHM = "HS256"

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="login")

# 패스워드를 암호화하는 함수
def get_password_hash(password: str) -> str:
    return pwd_context.hash(password)

# 암호화된 패스워드와 입력된 패스워드를 비교하는 함수
def verify_password(plain_password: str, hashed_password: str) -> bool:
    return pwd_context.verify(plain_password, hashed_password)


# JWT 생성 함수
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


# JWT 검증 함수
def decode_access_token(token: str):
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        return payload
    except JWTError:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid authentication credentials",
            headers={"WWW-Authenticate": "Bearer"},
        )

여기까지 해서 인증과 관련된 코드를 추가하였다. 이제 본격적으로 DB에 값을 넣고 로그인하고 데이터를 수정하는 작업을 진행할 것이다.

인증부분이 중요하기 때문에 따로 내용을 정리하였다. 다음 포스팅에서는 회원 가입하는 코드와 로그인하는 코드를 추가로 진행해보도록 하겠다.

728x90
반응형