Redis token 관리

김민찬·2025년 2월 23일

Hanmbook-stocks

목록 보기
6/7

원래 token 관리를 DB에서 했는데, Redis에서 해보기로 하였당

[Redis로 token을 관리하면 좋은 이유]

[Redis token 관리 전체 흐름]

1. 로그인 ➡️ 2. token 생성 ➡️ 3. front 의 localstorage에 저장(바뀔 수도) & redis 에 token 저장 ➡️ 4. 특정 요청 ➡️ 5. front의 Header에서 오는 token VS redis에 오는 token 비교

그림으로 표현하면

[구체적 단계]

1. 로그인

  • 유저가 로그인을 시도

2. token 생성

[로그인API 전체 코드]

# 로그인
@router.post("/login")
async def login(
    req: AuthSigninReq,
    db=Depends(get_db_session),
    jwtUtil: JWTUtil = Depends(),
    authService: AuthService = Depends(),
    redis_db=Depends(get_redis),
    redisService: RedisService = Depends(),
):

    user = authService.signin(db, req.login_id, req.pwd)
    if not user:
        raise HTTPException(status_code=401, detail="Login failed")

    access_token = jwtUtil.create_token(user.model_dump())
    # redis 에 토큰넣기
    await redisService.add_token(redis_db, access_token, req.login_id)

    return AuthResp(
        message="로그인 되었습니다.",
        user=user,
        access_token=access_token,  # front에 토큰을 응답으로 반환
    )

[create_token 함수 코드]

  • 로그인 API 요청이 오면, create_token 함수로 jwt token을 생성한다.
SECRET_KEY = "1234"  # 임시값
ALG = "HS256"

def create_token(
        self, payload: dict, expires_delta: timedelta | None = timedelta(minutes=30)
    ):
        payload_to_encode = payload.copy()
        expire = datetime.now(timezone.utc) + expires_delta
        payload_to_encode.update({"exp": expire})
        return jwt.encode(payload_to_encode, SECRET_KEY, algorithm=ALG)

[create_token 함수 -> jwt token 생성]

  • User DB 에서 가져온 데이터를 바탕으로 jwt token을 생성하는 모습
# 실제
    access_token = jwtUtil.create_token(user.model_dump())

3-1. Front의 localstorage에 저장

  • 이 return 값을 front 에서 받아서 localstorage에 저장
from pydantic import BaseModel

# 응답 class 정의
class AuthResp(BaseModel):
    message: str
    user: User
    access_token: str | None = None

# login API return 값
    return AuthResp(
        message="로그인 되었습니다.",
        user=user,
        access_token=access_token,  # front에 토큰을 응답으로 반환
    )
  • return 값을 받은 후, front의 localstorage에 저장하는 js 코드
const result = await response.json();

          if (response.ok) {
              alert("로그인 성공!");
              localStorage.setItem("token", result.access_token);  // JWT 토큰 저장

<참고>
front 에서 localstorage 에 저장하는게 맞는지 좋은지?
쿠키에 저장하는게 좋은지?

3-2. Redis에 저장

  • 생성된 jwt token 을 redis에 저장
# redis 에 토큰넣기
    await redisService.add_token(redis_db, access_token, req.login_id)
  • redis 저장 / 삭제 함수
from fastapi import HTTPException

# ✅ 토큰 유지시간 3600초 == 1시간
TTL = 3600


class RedisService:
    async def add_token(
        self,
        redis_db,
        token: str,
        login_id: str,
    ):
        await redis_db.set(token, login_id, ex=TTL)

    async def delete_token(self, redis_db, token):

        value = await redis_db.get(token)
        if value is None:
            raise HTTPException(status_code=404, detail="Redis token expired")

        await redis_db.delete(token)

✅ redis_db.set / redis_db.delete 는 비동기 함수 이므로 await를 사용

✅ redis 에 jwt token 저장시 key : value 형식을 token : login_id으로 함. ➡️ 이유는 밑에서 설명

4. 특정요청이 들어옴

  • ex) 로그인이 된 상태에서, MyPage 접근 or 게임시작 or GG or 매도 or 매수 etc

5. Token 검증

  • 특정 API요청이 들어오면, front 에서 보내는 token과 Redis에 있는 token을 비교해서 같은지 검증해야함.

5-1. 로그아웃 시 Token 검증

  • 로그아웃 API 전체코드
# 로그아웃
@router.post("/logout")
async def auth_logout(
    authorization: str = Header(None),
    redis_db=Depends(get_redis),
    redisService: RedisService = Depends(),
):  # 헤더에서 토큰을 받음
    if not authorization:
        raise HTTPException(status_code=401, detail="인증 토큰이 필요합니다.")

    token = authorization.split(" ")[1]  # "Bearer <토큰>"에서 토큰만 추출

    await redisService.delete_token(redis_db, token)

    return {"message": "로그아웃 되었습니다."}

로그아웃은 front의 Reqest를 통해 token을 받고, 이후에 redis 의 token 과 비교해서 로그아웃을 시켜준다.

이때, redis 는 key 값을 기반으로 delete를 할 수 있기 때문에, ⬆️위에서 말한 token값을 key값으로 설정하게 되었다.
login_id : token 형식으로 저장되는게 이상적이라고 생각하지만.. 이러한 이유로 token : login_id 형식으로 저장하게 되었다.

시행착오

원래 DB 에서 Token을 관리하던 코드를 바꾸는 상황이었다
Redis 에 Token을 넣는건 까지는 순조로웠으나, 로그아웃을 해도 토큰이 삭제가 안되는 문제가 계속 발생했다..

^
로그아웃 하면 나온 alert

delete_token 함수문제 인가 싶어서

    async def delete_token(self, redis_db, token):

        value = await redis_db.get(token)
        if value is None:
            raise HTTPException(status_code=404, detail="Redis token expired")

        await redis_db.delete(token)

redis_db.get 함수의 return 값을 받아보기 시도..
하지만 계속 동일한 문제가 발생했다.

그래서 Token 자체는 제대로 가져오는지 확인을 해보았는데, 프론트에서 Token을 제대로 가져오지 못하고 있는 사실을 발견했다!!!

프론트 코드는 건들인 것이 없기때문에, 그럼 애초에 프론트에서 Token값을 받지 못한게 아닐까?
하고 로그인API의 return 값을 살펴보았지만.. 아무런 문제가 없어보였다.

토큰 반환 하잖아!!!!

await 문제인가..??? 싶었음..

그렇게 1시간 정도 삽질을 하다가 AuthResp 의 내용을 바꾸지 않은걸 깨달았다..
-> 근데 원래 DB 에서 Token보내줄때랑 차이가 없는데, 왜 AuthResp가 바껴잇었지??

profile
동까스

0개의 댓글