[Node.js/Express.js] Session, Interceptor 기능 개발 - 설계

djawnstj·2023년 4월 2일
1
post-thumbnail

블로그 이전

입사 하자마자 진행했던 프로젝트인 paperless 회진 앱의 보안 점검 결과로 여러 문제점을 지적 당했다.
크게 앱 단계의 문제와 서버 단계의 문제로 나눌 수 있었는데, 서버쪽 문제는

  1. 세션 미적용
  2. SQL Injection

문제가 있었다.

이중 세션은 사실 JS의 Map변수를 이용해 로그인 하는 시점에 생성, setTimeout()을 통해 30분 후 해당 세션 삭제와 매 요청시 setTimeout()의 key값으로 clearTimeout()을 호출하고, 다시 30분 시작을 하도록 구현했었다.

테스트시에는 문제가 없었지만, 실제 서버에 올려보니 30분이 되지 않았는데 세션이 삭제되는 등 많은 문제가 있었다.

개발 당시에는 내가 굉장히 무지(?)했기 때문에 여러 상황을 고려하지 못했는데 지금 생각해보면 당연히 동시성 문제가 나도록 개발을 했다.
대충 코드를 보면

const sessionUtil = {
    map: new Map(),
    isValid(key) {
        return this.map.has(key)
    },
    reset(key) {
        const newVar = this.map.get(key);
        clearTimeout(newVar.timeout);
        newVar.timeout = setTimeout(() => this.map.delete(key), 30000);
    },
    login(key) {
        const newVar = {};
        newVar.timeout = setTimeout(() => this.map.delete(key), 30000);
        this.map.set(key, newVar);
    }
}

이런식으로 만들어두고 요청이 들어오면 isValid()를 통해 로그인 여부 체크와 reset()을 통해 만료시간을 초기화하도록 구성했다.

개발은 내가했지만, 서버 운영은 회사 선임개발자분이 관리하셨기 때문에 나도 모르는 사이 해당 기능을 다 막아두셨더라. 그래서 보안점검에 걸리게 되었다.

사실 이런 기능들은 프레임워크에서 당연히 지원해야 한다고 생각해왔는데 Express.js로 만들어진 사내 프레임워크는 이런 부분이 없어 아쉬웠었다.

어쨌든 이번 기회에 세션 기능을 보완하기로 했고 추가적으로 지금까진 Controller의 모든 함수 시작부분에 일일이 isValid()reset()을 호출해뒀는데 인터셉터도 개발해 앞으로는 이런 수고를 덜게끔 기능 개발을 했다.

설계

시작하기 앞서 일단 나는 JavaScript, Express.js를 잘 알지 못한다. 그리고 주로 Spring Framework를 하다보니 구현하려는 기능 또한 Spring과 유사한 점이 매우 많다.

개인적으로 서버는 신뢰성이 중요하고 개발자의 의도대로 프로그램이 작동되도록 해야한다고 생각한다. 그래서 interface를 이용하여 신뢰성을 높이고 다형성을 이용하는 등 객체지향적 개발을 하려했지만, 아쉽게도 사내 프레임워크가 JavaScript 100%로 개발되어있었고 Node 표준 규칙(CommonJS)을 지키고 있었기 때문에 설계에 한계가 있었다.

세션


세션이라는 객체는 인스턴스 변수로 Map을 통해 key-value로 이용자별 저장할 정보들을 담도록 하고, 이 세션을 저장소에 저장하는 방식으로 구상했다.
이때 저장소는 기본적으로 프레임워크가 MemoryStoreRedisStore 이렇게 2가지 방식을 기본적으로 지원하게끔 개발했다.

설정을 따로 하지 않으면 기본값으로 MemoryStore를 사용하게끔 하고 사용자가 Redis 설정값을 넣어두고, 설정정보를 담는 객체에 RedisStore를 사용하는 설정을 넣으면 RedisStore를 사용하도록 했다.

또한 interface는 아니지만 기본 SessionStore class를 만들어 해당 class를 상속받아 개발자가 SessionStore를 추가적으로 개발할 수 있도록 강제했다.

인터셉터


인터셉터도 기본적으로 interface로 구현하고싶었지만 상황이 불가능했기에 SessionStore와 마찬가지로 HandlerInterceptor class를 두고 해당 class를 상속해 개발자가 추가적인 Interceptor를 사용할 수 있게끔 강제했다..

설정에 Interceptor를 등록할땐 적용할 path와 제외할 path, 그리고 호출될 순서를 설정할 수 있게 개발했다.

여태 한 개발은 단순히 이용자가 '실제 애플리케이션을 이용하는 이용자' 였는데 이번 개발에서는 '실제 애플리케이션을 이용하는 이용자'와 '이 프레임워크를 통해 개발하는 개발자' 까지 포함되다 보니 조금 더 고려할 사항이 많았다.

하지만 정말 오랜만에 계획에서부터 신나는 느낌을 받아 너무 재밌게 개발을 했다.

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

0개의 댓글