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을 생성해서 클라이언트로 전송하도록 하였습니다.