Redis로 Refresh token을 클라우드에 저장하는 시도를 해보았다.
Redis 사용법 포스팅
const redisClient = redis.createClient({
url: `redis://${process.env.REDIS_USERNAME}:${process.env.REDIS_PASSWORD}@${process.env.REDIS_HOST}:${process.env.REDIS_PORT}/0`,
legacyMode: true,
});
redisClient.on("connect", () => {
console.info("Redis connected!");
});
redisClient.on("error", (err) => {
console.error("Redis Client Error", err);
});
redisClient.connect().then();
const redisCli = redisClient.v4;
await redisCli.set(refreshToken, userId);
res.cookie("accessToken", `Bearer ${accessToken}`);
res.cookie("refreshToken", `Bearer ${refreshToken}`);
set(key, value)
메서드를 사용해서 refreshToken을 key로, userId를 value로 Redis에 넣어준다.const redisClient = redis.createClient({
url: `redis://${process.env.REDIS_USERNAME}:${process.env.REDIS_PASSWORD}@${process.env.REDIS_HOST}:${process.env.REDIS_PORT}/0`,
legacyMode: true,
});
redisClient.on("connect", () => {
console.info("Redis connected!");
});
redisClient.on("error", (err) => {
console.error("Redis Client Error", err);
});
redisClient.connect().then(); // redis v4 연결 (비동기)
const redisCli = redisClient.v4;
if (!isRefreshTokenValid) {
await redisCli.del(authRefreshToken);
return res
.status(419)
.json({ message: "Refresh Token이 만료되었습니다." });
}
if (!isAccessTokenValid) {
const accessTokenId = await redisCli.get(authRefreshToken);
if (!accessTokenId)
return res.status(419).json({
message: "Refresh Token의 정보가 서버에 존재하지 않습니다.",
});
newAccessToken = createAccessToken(accessTokenId);
res.cookie("accessToken", `Bearer ${newAccessToken}`);
return res.json({ message: "Access Token을 새롭게 발급하였습니다. 다시 시도하십시오." });
}
const { userId } = getAccessTokenPayload(authAccessToken);
const user = await Users.findOne({ where: { userId } });
res.locals.user = user
next();
res.locals.user
에 담아준다.이렇게 만들었을 때 일단 처음 redis와 connect을 하는 코드가 겹친다. 그래서 리팩토링을 해주었다.
class RedisClient {
constructor() {
this.redisClient = redis.createClient({
url: `redis://${process.env.REDIS_USERNAME}:${process.env.REDIS_PASSWORD}@${process.env.REDIS_HOST}:${process.env.REDIS_PORT}/0`,
legacyMode: true,
});
this.redisConnected = false;
}
async initialize() {
this.redisClient.on("connect", () => {
this.redisConnected = true;
console.info("Redis connected!");
});
this.redisClient.on("error", (err) => {
console.error("Redis Client Error", err);
});
if (!this.redisConnected) this.redisClient.connect().then(); // redis v4 연결 (비동기)
}
setRefreshToken = async (refreshToken, userId) => {
await this.initialize();
await this.redisClient.set(refreshToken, userId);
};
getRefreshToken = async (refreshToken) => {
await this.initialize();
const token = await this.redisClient.get(refreshToken);
return token;
};
deleteRefreshToken = async (refreshToken) => {
await this.initialize();
await this.redisClient.del(refreshToken);
};
}
this.redisConnected
라는 boolean 변수를 만들고, 이 값이 false일 때만 connect를 진행하는 식으로 에러를 고칠 수 있었다.redisClient = new RedisClient();
를 통해 인스턴스를 만들었고, 기존 redis 메서드들을 클래스에서 정의한 함수들로 대체해주었다.1) Repository Layer (실제 Redis에 접근하는 layer): RedisClient Class를 users repository로 넣어주고, 객체를 이용해서 두 class를 export 해준다. module.exports = { UserRepository, RedisClientRepository };
2) Service Layer: RedisClientRepository를 import하고 관련 함수들을 정의해준다. 데이터 가공 과정이 필요한 경우 이 layer에서 해준다.
3) Controller Layer: RedisClientService를 import하고 UserController class 내에서 인스턴스를 만들어준다. redisClient = new RedisClientService();
인스턴스를 통해 Redis의 메서드들을 해당 layer에서 사용한다.
if문으로 validation을 해주었다.
if (typeof nickname !== "string")
throw new Error("412/닉네임의 형식이 일치하지 않습니다.");
if (password !== confirmedPassword)
throw new Error("412/패스워드가 일치하지 않습니다.");
if (password.length < 4 || typeof password !== "string")
throw new Error("412/패스워드 형식이 일치하지 않습니다.");
if (password.includes(nickname.toLowerCase()))
throw new Error("412/패스워드에 닉네임이 포함되어 있습니다.");
const nickNameRegex = new RegExp("^[a-zA-z0-9]{3,}$", "g");
if (!nickNameRegex.test(nickname))
throw new Error("412/닉네임의 형식이 일치하지 않습니다.");
joi
사용const { nickname, password } = await signupSchema
.validateAsync(req.body)
.catch((error) => {
console.error(error);
throw new Error(`412/${error}`);
});
joi.js
파일signupSchema: Joi.object({
nickname: Joi.string()
.regex(/^[a-zA-Z0-9]{3,}$/)
.messages({
"string.base": "닉네임의 형식이 일치하지 않습니다.",
"string.pattern.base": "닉네임의 형식이 일치하지 않습니다.",
"string.empty": "닉네임의 형식이 일치하지 않습니다.",
"string.min": "닉네임의 형식이 일치하지 않습니다.",
}),
password: Joi.string().min(4).required().messages({
"string.base": "패스워드의 형식이 일치하지 않습니다.",
"string.empty": "패스워드 형식이 일치하지 않습니다.",
"string.min": "패스워드 형식이 일치하지 않습니다.",
}),
confirmedPassword: Joi.string().valid(Joi.ref("password")).required().messages({
"string.base": "패스워드가 일치하지 않습니다.",
"any.only": "패스워드가 일치하지 않습니다.",
"string.empty": "패스워드가 일치하지 않습니다.",
}),
}),