Image_Us-리프레시 토큰

codakcodak·2023년 2월 27일
0

ImageUs

목록 보기
8/17

문제상황

  • 로그인시 서버에서 발급한 access_token은 만료시간이 되면 사용이 불가하기 때문에 클라이언트 입장에서는 다시 로그인 요청을 해야한다.

  • 한 유저가 여러번 로그인 했을 때 받은 리프레시 토큰이 모두 유효하게 작동한다면 보안에 문제가 생긴다.

  • jwt_token은 secret_key를 몰라도 내용물을 볼 수 있어
    secret_key가 노출된다면 악의적으로 모든유저의 access_token을 생성 할 수 있다.

해결방법

  • 로그인 시 refresh_token을 생성하는데 매번 secret_key를 다르게 생성 및 db에 저장하여 한 유저의 다중 refresh_token 사용을 막는다.

  • 클라이언트 입장에서 정해진 시간에 refresh_token을 예약하도록
    반환값에 만료시간을 함께 전달한다.

적용과정

1.로직 순서도

2.리프레시토큰을 생성 할 secret_key의 스키마 추가

CREATE TABLE `users_token_auth` (
  `user_id` int NOT NULL,
  `refresh_token_secret_key` varchar(255) NOT NULL,
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `deleted` boolean not null DEFAULT 0,
  PRIMARY KEY (`user_id`)
)

*user_id를 고유값으로 명시하여 사용자별 하나의 refresh_token_secret_key를 가지도록 설정

3.user_service에 refresh_token을 생성하는 로직구현

#전체코드
def generate_token(self,user_id):
        user_token_auth_info=self.user_dao.get_user_token_auth(user_id)
		refresh_token_len=self.config['JWT_REFRESH_TOKEN_NUM']

        refresh_token_secret_key=(''.join(random.choice(string.ascii_letters + string.digits) for _ in range(refresh_token_len)))
        if user_token_auth_info ==None:
            result=self.user_dao.insert_user_token_auth(user_id,refresh_token_secret_key)

        elif user_token_auth_info['deleted']==1:
            result=self.user_dao.update_user_token_auth(user_id,{'deleted':0,'refresh_token_secret_key':refresh_token_secret_key})

        else:
            result=self.user_dao.update_user_token_auth(user_id,{'refresh_token_secret_key':refresh_token_secret_key})

        print(result)
        
        jwt_access_token_expire_time= timedelta(seconds=self.config['JWT_ACCESS_TOKEN_EXPIRE_TIME'])
        jwt_refresh_token_expire_time= timedelta(seconds=self.config['JWT_REFRESH_TOKEN_EXPIRE_TIME'])

        time_now=datetime.now()

        access_token_expire=time_now+jwt_access_token_expire_time
        refresh_token_expire=time_now+jwt_refresh_token_expire_time
        
        
        access_token_payload={
            'user_id':user_id,
            'exp':access_token_expire,
            'iat':time_now
        }
        
        refresh_token_payload={
            'user_id':user_id,
            'exp':refresh_token_expire,
            'iat':time_now
        }
        
        access_token=jwt.encode(access_token_payload,self.config['JWT_SECRET_KEY'],'HS256')
        refresh_token=jwt.encode(refresh_token_payload,refresh_token_secret_key,'HS256')
        
        return {
            'access_token':access_token,
            'access_token_expire_time':access_token_expire.strftime('%Y-%m-%d %H:%M:%S'),
            'refresh_token':refresh_token,
            'refresh_token_expire_time':refresh_token_expire.strftime('%Y-%m-%d %H:%M:%S')
        }
  • db에서 기존의 refresh_token을 만든 secret_key가 존재하지 않으면 새롭게 생성하여 db에 저장하고,이미 존재한다면 정보를 호출
user_token_auth_info=self.user_dao.get_user_token_auth(user_id)

if user_token_auth_info ==None:
      result=self.user_dao.insert_user_token_auth(user_id,refresh_token_secret_key)
  • 클라이언트입장에서 access_token을 정해진 시각에 얻도록 요청할 수 있게 미리 설정한 토큰의 만료 시간,user_id,생성시각을 내용으로 저장
		jwt_access_token_expire_time= timedelta(seconds=self.config['JWT_ACCESS_TOKEN_EXPIRE_TIME'])
        jwt_refresh_token_expire_time= timedelta(seconds=self.config['JWT_REFRESH_TOKEN_EXPIRE_TIME'])

        time_now=datetime.now()

        access_token_expire=time_now+jwt_access_token_expire_time
        refresh_token_expire=time_now+jwt_refresh_token_expire_time
        
        
        access_token_payload={
            'user_id':user_id,
            'exp':access_token_expire,
            'iat':time_now
        }
        
        refresh_token_payload={
            'user_id':user_id,
            'exp':refresh_token_expire,
            'iat':time_now
        }
  • datetime을 str로 변경 후 최종 반환 데이터 정리
return {
            'access_token':access_token,
            'access_token_expire_time':access_token_expire.strftime('%Y-%m-%d %H:%M:%S'),
            'refresh_token':refresh_token,
            'refresh_token_expire_time':refresh_token_expire.strftime('%Y-%m-%d %H:%M:%S')
        }

결과

  • 프론트에서는 반환형에 포함된 token의 만료시각으로 미리 예약 설정 가능

  • refresh_token을 생성할 때마다 새로운 secret_key를 적용하기 때문에 새로운 기기에서 로그인하여 refresh_token을 생성하면 전에 있던 refresh_token은 사용하지 못하여 보안에 더욱 강화

profile
숲을 보는 코더

0개의 댓글