Refresh Token을 적용하기전 미리 결정한 체크리스트는 다음과 같습니다.
Refresh Token
의 Payload
에는 탈취당했을 경우를 대비하여 특정할 수 있는 정보를 담지 않기Payload
에 담지 않기 때문에, Redis
나 DB
를 사용하여 Key(토큰) - Value(유저 정보)의 형태로 관리. 이를 통해 새로운 Access Token
을 발급할 때 필요한 정보를 활용하기Access Token
의 주기가 짧을 경우 잦은 DB 조회가 발생. 이를 해결하기 위해 DB 사용과 더불어 메모리를 이용한 캐싱을 사용하기Refresh Token
의 만료 주기는 14일
로 설정. 만료된 토큰은 캐시와 DB에서 폐기하기Refresh Token
폐기하기async createRefreshToken(): Promise<Record<string, string>> {
const refreshTokenUuid = generateUuid();
const refreshToken = await this.jwtService.signAsync(
{ refreshTokenUuid },
{ secret: jwtConstants.refreshSecret, expiresIn: '14d' },
);
return { refreshToken, refreshTokenUuid };
}
createRefreshTokenData(refreshTokenUuid: string, userUuid: string) {
const currentDate = new Date();
const expiryDate = new Date(currentDate);
expiryDate.setDate(currentDate.getDate() + REFRESH_TOKEN_EXPIRY_DAYS);
const refreshTokenData: TokenData = {
token: refreshTokenUuid,
expiry_date: expiryDate,
user_id: userUuid,
};
return refreshTokenData;
}
async login(userUuid: string) {
const { refreshToken, refreshTokenUuid } = await this.createRefreshToken();
const accessToken = await this.createAccessToken(userUuid);
const refreshTokenData = this.createRefreshTokenData(
refreshTokenUuid,
userUuid,
);
super.create(refreshTokenData);
const tokenData = {
access_token: accessToken,
refresh_token: refreshToken,
};
return ResponseUtils.createResponse(HttpStatus.OK, tokenData);
}
Payload
에는 랜덤 uuid
만 담아 각각의 토큰이 외부에서는 누구의 토큰인지 알 수 없도록 구현하였습니다.Cache는 Redis가 아닌 자체적으로 적용한 캐시를 사용하도록 하였습니다.
Refresh Token
생성 시 Cache에 key는 생성 시 Payload
에 담아져 있는 uuid
를 key
로 두었고, value는 DB에 저장된 아래의 내용을 담았습니다.
const refreshTokenData: TokenData = {
token: refreshTokenUuid,
expiry_date: expiryDate,
user_id: userUuid,
};
캐시
를 먼저 탐색 한 뒤 없으면 DB
를 탐색하여 캐시에 저장 및 리턴하도록 구현하였고, 유효기간이 지나지 않았을 경우엔 user_id
를 기반으로 Access Token
을 생성해서 클라이언트로 전송하도록 하였습니다.