블로그 이전

Redis로 세션을 저장하는 기능을 구현했지만, 흐름상 쿠키에 세션 key를 담아줄 경우 프록시서 버에서 쿠키를 탈취해 해당 key로 요청을 날리는건 마찬가지라는 생각을 했다.

요즘 JWT 같은 방법으로 인증관리를 구현한다고 알고있지만 아직 내가 잘 알지 못하고, 회사 분위기도 해오던 방법을 거스르는것에 반감이 있다보니 세션을 유지하고자 했다.
JWT에 대해 알아볼때 봤던것이 쿠키에 httpOnly옵션을 줘서 클라이언트에서만 쿠키에 접근할 수 있도록 한다는 것을 참고해서 내가 구현한 세션/쿠키 방식에도 httpOnly 옵션을 추가하기로 했다.

쿠키 옵션 추가

기존 RedisSessionStore - geSession()

/**
 * @param { string } key
 * @param { Response } res
 * @param { boolean } status
 * @returns { Promise<HttpSession> }
 */
getSession = async (key, res, status) => {
    let obj;

    if (key) obj = await this.#client.get(key, (err) => {
        console.error("RedisSessionFactory getAttribute error: " + err)
    });

    if (!obj && status) {
        key = await this.#createSession();
        obj = await this.#client.get(key, (err) => {
            console.error("RedisSessionFactory getAttribute error: " + err)
        });

        res.cookie(SessionStore.sessionKey, key);
    } else if (!obj && !status) {
        res.clearCookie(SessionStore.sessionKey);
        return null;
    }

    await this.#client.multi()
        .expire(key, SessionStore.expireTime)
        .exec();

    obj = JSON.parse(obj);
    const map = new Map(obj.map(([mapKey, mapValue]) => [mapKey, mapValue]));

    return new RedisSession(key, map, this.#saveSession);
}

RedisSessionStore에서 getSession()부분을 보면 res.cookie(SessionStore.sessionKey, key) 부분에 옵션을 추가하면 된다.

이왕 옵션을 추가하는 김에 maxAge도 설정해 세션 저장소와 생명주기를 같게 맞춰주려 한다.

res.cookie(SessionStore.sessionKey, key, {
	httpOnly: true,
	maxAge: SessionStore.expireTime * 1000
})

이렇게 바꿔주면 옵션을 적용된다.
하지만 문제는 MemorySessionStore도 따로 변경해줘야 한다는 것이다.

이 부분 개발을 할때 개인적으로 느낀게 getSession()이 너무 많은 역할과 책임을 갖고있다는 것이었다.

그래서 쿠키를 건드리는 부분은 따로 빼주기로 했다.

RedisSessionStore 수정

/**
 * @param { string } key
 * @param { Response } res
 * @param { boolean } status
 * @returns { Promise<any[]> }
 */
getSession = async (key, res, status) => {
    let obj;

    if (key) obj = await this.#client.get(key, (err) => {
            console.error("RedisSessionFactory getAttribute error: " + err)
        });

    if (!obj && status) {
        key = await this.#createSession();
        obj = await this.#client.get(key, (err) => {
            console.error("RedisSessionFactory getAttribute error: " + err)
        });
    } else if (!obj && !status) {
        return [null, key];
    }

    await this.#client.multi()
        .expire(key, SessionStore.expireTime)
        .exec();

    obj = JSON.parse(obj);
    const map = new Map(obj.map(([mapKey, mapValue]) => [mapKey, mapValue]));

    return [new RedisSession(key, map, this.#saveSession), key];
}

이렇게 쿠키를 건드리는 부분은 모두 걷어내고 배열로 세션과 key를 반환해주도록 했다.

MemorySessionStore 수정

/**
 * @param { string } key
 * @param { Response } res
 * @param { boolean } status
 * @returns { any[] }
 */
getSession = (key, res, status = true) => {

    let session;

    if (key) session = this.#map.get(key);

    if (!session && status) {
        key = this.#createSession();
        session = this.#map.get(key);

    } else if (!session && !status) {
        return [null, key];
    }

    return [session, key];

}

MemorySessionStore도 마찬가지로 수정했다.

이제 쿠키를 생성, 제거해줄 역할만 만들어주면 된다.

SessionFactory 수정

/**
 * @param { string } key
 * @param { Response } res
 * @param { boolean } status
 * @returns { HttpSession }
 */
getSession = async (key, res, status) => {
    const [httpSession, returnKey] = await this.#sessionStore.getSession(key, res, status);
    if (httpSession) res.cookie(SessionStore.sessionKey, returnKey, {
        httpOnly: true,
        maxAge: SessionStore.expireTime * 1000
    });
    else {
        res.clearCookie(SessionStore.sessionKey);
        await this.#sessionStore.removeSession(returnKey);
    }

    return httpSession;
}

SessionStore에서 세션을 가져오는 SessionFactory에서 쿠키를 설정해주기로 했다.
세션 저장소에서 반환받은 세션을 기준으로 세션이 존재하면 쿠키 생성, 연장을 해주고 존재하지 않으면 쿠키 제거, 세션 저장소에서 세션 제거를 해주도록 했다.

이젠 추가적으로 세션 저장소를 개발해도 쿠키는 굳이 관리해줄 필요가 없어졌다.

profile
이용자가 아닌 개발자가 되자!

0개의 댓글