Programmers

[27일차]Node.js 프로젝트 구조, 암호화 인증 시스템

PARKpatchnotes 2025. 10. 17. 14:16

1. Node.js 프로젝트 구조 설계

효율적인 개발과 유지보수를 위해 프로젝트의 구조를 체계적으로 설계하는 것은 필수적이다. 본 프로젝트는 계층형 아키텍처(Layered Architecture)를 기반으로 구조를 설계했다.

프로젝트 구조

.
├── database/         # 데이터베이스 연결 및 설정
├── middleware/       # 인증, 유효성 검사 등 미들웨어
├── modules/          # 기능별 모듈 (도메인)
│   └── users/
│       ├── user.controller.js
│       ├── user.service.js
│       └── user.repository.js
├── routes/           # API 라우팅 설정
├── utils/            # 에러 핸들러, 토큰 생성 등 유틸리티
└── app.js            # Express 애플리케이션 초기화

구조 설계 이유

각 디렉터리와 파일은 관심사의 분리(Separation of Concerns, SoC) 원칙에 따라 역할을 나눈다.

  • routes: API 엔드포인트 정의와 HTTP 요청을 수신하는 역할만 담당한다. 요청 유효성 검사 미들웨어를 호출하고, 최종적으로 Controller의 함수와 연결된다.

  • middleware: routescontroller 사이에서 공통적으로 필요한 기능을 처리한다. 예를 들어, authenticateJWT 미들웨어는 사용자의 JWT를 검증하여 인증 상태를 확인하고, validate 미들웨어는 express-validator를 통해 요청 데이터의 유효성을 검사한다.

  • modules: 애플리케이션의 핵심 기능(도메인)을 모듈 단위로 묶는다. users 모듈은 다음과 같은 3개의 계층으로 구성된다.

    1. Controller: HTTP 요청(req)과 응답(res)을 직접 처리한다. 클라이언트로부터 받은 데이터를 정제하여 Service 계층으로 전달하고, Service로부터 받은 결과를 클라이언트에 응답한다.
    2. Service: 애플리케이션의 핵심 비즈니스 로직을 수행한다. 데이터의 가공, 계산, 외부 API 호출 등 복잡한 작업을 처리하며, 데이터베이스 접근이 필요할 때는 Repository 계층을 호출한다.
    3. Repository: 데이터베이스와의 모든 상호작용을 담당한다. SQL 쿼리를 실행하고, 그 결과를 Service 계층으로 반환한다. 데이터베이스 종류가 변경되더라도 이 계층만 수정하면 되므로 유연성이 높다.

계층형 구조의 장점

  1. 유지보수성 향상: 각 계층은 독립적인 역할을 수행하므로, 특정 기능의 수정이 필요할 때 해당 계층의 코드만 집중해서 보면 된다. 예를 들어, 비즈니스 로직 변경은 Service에서, DB 쿼리 최적화는 Repository에서 수행할 수 있다.

  2. 코드 재사용성 증가: Service나 Repository의 함수는 여러 Controller에서 재사용될 수 있다.

  3. 테스트 용이성: 각 계층을 독립적으로 테스트하기 용이하다. 예를 들어, Service 계층을 테스트할 때 실제 DB에 연결할 필요 없이 Repository를 모의(Mock) 객체로 대체하여 비즈니스 로직의 정확성을 검증할 수 있다.

  4. 팀 협업 효율 증대: 역할 분담이 명확해져 여러 개발자가 동시에 각기 다른 계층을 작업하기 용이하다.


2. 비밀번호 암호화

암호화의 필요성

사용자의 비밀번호를 데이터베이스에 평문(Plain Text)으로 저장하는 것은 매우 위험하다. 만약 데이터베이스가 해킹당할 경우, 모든 사용자의 계정 정보가 그대로 유출되어 2차 피해로 이어질 수 있다. 따라서 비밀번호는 반드시 암호화하여 복원할 수 없는 형태로 저장해야 한다.

해싱(Hashing)이란

해싱은 임의의 길이 데이터를 고정된 길이의 데이터(해시 값)로 매핑하는 일방향 함수다. 비밀번호 암호화에 해싱을 사용하는 이유는 다음과 같다.

  • 단방향성(One-way): 해시 값에서 원본 데이터를 복원하는 것(복호화)이 수학적으로 거의 불가능하다.
  • 결정론적(Deterministic): 같은 입력값에 대해서는 항상 같은 해시 값이 출력된다.

로그인 시에는 사용자가 입력한 평문 비밀번호를 다시 해싱한 후, 데이터베이스에 저장된 해시 값과 비교하여 일치 여부를 판단한다.

Crypto vs. Bcrypt

Node.js에서 비밀번호 해싱에 주로 사용되는 라이브러리는 내장 모듈인 crypto와 서드파티 라이브러리인 bcrypt가 있다.

구분 crypto (e.g., scrypt, pbkdf2) bcrypt
특징 Node.js 내장 모듈. 별도 설치 불필요. 다양한 알고리즘 지원. 비밀번호 해싱을 위해 특별히 설계된 라이브러리.
주요 기능 scrypt, pbkdf2 등 키 유도 함수(KDF) 제공. Salt 자동 생성 및 해시 값에 포함.
장점 - 별도 설치가 필요 없다.
- 다양한 암호화 알고리즘 선택 가능.
- Salt 관리가 매우 간편하다.
- 구현이 직관적이고 간단하다.
단점 - Salt를 직접 생성하고 관리해야 하므로 구현이 복잡하다.
- 잘못 구현할 경우 보안에 취약해질 수 있다.
- C++ 기반 네이티브 모듈이므로, 특정 환경에서 설치 시 빌드 오류가 발생할 수 있다. (이 경우 bcrypt.js로 대체 가능)
  • Salt: 해싱 과정에 추가되는 임의의 데이터로, 동일한 비밀번호라도 Salt 값이 다르면 해시 결과가 달라진다. 이는 레인보우 테이블 공격을 방지하는 데 필수적이다.

Bcrypt를 선택한 이유

본 프로젝트에서는 다음과 같은 이유로 bcrypt를 선택했다.

  1. 구현의 단순성과 안전성: bcryptSalt를 자동으로 생성하여 해시 값에 포함시켜주므로, 개발자가 Salt를 별도로 저장하고 관리할 필요가 없다. 이로 인해 구현이 매우 간단해지며, Salt 관리 미흡으로 인한 보안 사고를 원천적으로 방지할 수 있다.

  2. 검증된 안정성: bcrypt는 오랜 기간 비밀번호 해싱의 표준으로 사용되어 왔으며, 그 안정성과 보안성이 널리 검증되었다.

  3. 속도 조절 기능(Cost Factor): bcrypt.hash 함수는 saltRounds라는 인자를 통해 해싱에 소요되는 시간을 의도적으로 조절할 수 있다. 하드웨어 성능이 발전하더라도 이 값을 높여 무차별 대입 공격(Brute-force attack)에 대한 저항력을 유지할 수 있다.

설치 과정에서 발생할 수 있는 잠재적 빌드 이슈에도 불구하고, bcrypt가 제공하는 구현의 편의성과 강력한 보안성은 이러한 단점을 상쇄하고도 남는다고 판단했다.


3. 결론

본 프로젝트는 명확한 HTTP 상태 코드 반환, 계층형 아키텍처 도입, 그리고 bcrypt를 이용한 안전한 비밀번호 암호화를 통해 안정적이고 확장 가능하며 보안이 강화된 백엔드 시스템의 기반을 마련했다. 특히 각 모듈의 역할을 명확히 분리함으로써, 향후 기능 추가 및 변경에 유연하게 대응할 수 있는 구조를 갖추게 되었다.