ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 세션 기반 인증의 이론 및 전체 과정
    배움/백엔드 2025. 5. 31. 12:00
    반응형

     

    세션 기반 로그인을 NestJS, PostgraSQL 기반 서비스에 구현해보다가 다음과 같은 답을 얻어 공유한다.

     


     

    HTTP 프로토콜은 기본적으로 상태가 없는(Stateless) 프로토콜입니다. 즉, 각 요청은 독립적으로 처리되며 서버는 이전 요청에 대한 정보를 기억하지 못합니다. 사용자가 로그인한 후 여러 페이지를 이동하거나 다른 요청을 보낼 때, 서버가 해당 사용자가 누구인지 계속 인식하게 하려면 "상태"를 유지하는 방법이 필요합니다. 이것이 바로 **세션(Session)**의 역할입니다.

    1. 세션 (Session) 이란?

    • 정의: 사용자가 웹사이트와 상호작용하는 동안 서버 측에 생성되고 유지되는 사용자별 정보 저장소입니다.
    • 목적: 여러 HTTP 요청에 걸쳐 사용자의 상태(예: 로그인 상태, 장바구니 내용 등)를 기억하고 유지합니다.
    • 세션 ID: 각 세션은 고유한 세션 ID로 식별됩니다. 이 세션 ID는 클라이언트(주로 웹 브라우저)와 서버 간에 공유되어 특정 사용자의 세션 데이터를 찾아 연결하는 데 사용됩니다.

    2. 쿠키 (Cookie)의 역할

    • 세션 ID 전달 매개체: 서버가 생성한 세션 ID는 클라이언트에게 전달되어야 하며, 클라이언트는 이후 요청마다 이 세션 ID를 서버에 다시 보내야 합니다. 이 전달 과정에서 주로 쿠키가 사용됩니다.
    • Set-Cookie 헤더: 로그인 성공 시, 서버는 HTTP 응답 헤더에 Set-Cookie를 포함하여 세션 ID가 담긴 쿠키를 클라이언트(브라우저)에게 보냅니다.
      • 예: Set-Cookie: connect.sid=??? Path=/; HttpOnly; Max-Age=86400
    • Cookie 헤더: 브라우저는 이 쿠키를 저장했다가, 동일한 도메인으로 다음 요청을 보낼 때 HTTP 요청 헤더에 Cookie를 포함하여 저장된 세션 ID를 자동으로 서버에 전송합니다.
    • 주요 쿠키 속성:
      • HttpOnly: JavaScript에서 쿠키에 접근하는 것을 막아 XSS 공격으로부터 보호합니다. (세션 쿠키에 필수)
      • Secure: HTTPS 연결에서만 쿠키가 전송되도록 합니다. (배포 환경에서 필수)
      • Max-Age 또는 Expires: 쿠키의 만료 시간을 설정합니다.
      • Path: 쿠키가 유효한 서버 경로를 지정합니다. (보통 /로 설정하여 전체 사이트에서 사용)
      • SameSite: CSRF 공격을 방지하는 데 도움이 되는 속성입니다 (Strict, Lax, None).

    3. 세션 스토어 (Session Store)

    • 세션 데이터 저장소: 실제 사용자별 세션 데이터(예: serializeUser를 통해 저장된 사용자 ID)는 서버 측의 세션 스토어에 저장됩니다.
    • 종류:
      • 메모리 스토어 (기본값): 개발 중에는 편리하지만, 서버가 재시작되면 모든 세션 데이터가 사라지고, 여러 서버 인스턴스로 확장할 수 없습니다.
      • 데이터베이스 스토어 (예: PostgreSQL, Redis): 프로덕션 환경에서는 데이터의 영속성과 확장성을 위해 데이터베이스 기반의 세션 스토어를 사용합니다. (현재 프로젝트에서는 PostgreSQL과 connect-pg-simple 어댑터를 사용하고 계십니다.)
    • 세션 ID를 키(key)로 사용하여 해당 세션 데이터를 조회하거나 저장합니다.

    4. 인증 흐름 상세 과정 (로그인 시)

    [클라이언트] ---> [서버: NestJS/Passport] ---> [세션 스토어: PostgreSQL] ---> [데이터베이스: User 정보]

    1. 사용자 자격 증명 제출 (클라이언트 → 서버)
      • 사용자가 웹 양식이나 API 클라이언트(Postman 등)를 통해 아이디와 비밀번호를 입력하고 로그인 버튼을 클릭합니다.
      • 클라이언트는 이 정보를 담아 서버의 로그인 API 엔드포인트(예: POST /auth/login)로 HTTP 요청을 보냅니다.
    2. 서버 자격 증명 유효성 검사 (NestJS/Passport)
      • AuthController: 해당 라우트의 컨트롤러 메소드가 요청을 받습니다.
      • @UseGuards(LocalAuthGuard): 이 데코레이터가 LocalAuthGuard를 실행합니다.
      • LocalAuthGuard: AuthGuard('local')을 상속하며, Passport의 'local' 인증 전략을 트리거합니다.
      • LocalStrategy: validate(username, password) 메소드가 실행됩니다.
        • AuthService.validateUser(username, password)를 호출하여 데이터베이스에 저장된 사용자 정보와 입력된 정보를 비교합니다 (보통 bcrypt.compare 사용).
      • validateUser가 유효한 사용자 정보를 반환하면 (보통 비밀번호 제외), LocalStrategy는 이 사용자 객체를 반환합니다.
    3. 세션 수립 (Passport)
      • LocalStrategy.validate()가 사용자 객체를 성공적으로 반환하면, AuthGuard는 인증이 성공했다고 판단합니다.
      • 세션 지원이 활성화된 경우 (PassportModule.register({ session: true })), AuthGuard는 내부적으로 req.login(user, callback) 함수를 호출합니다. 이 함수는 Passport에 의해 request 객체에 추가된 함수입니다.
    4. 세션 직렬화 (Serialization)
      • req.login() 함수는 등록된 SessionSerializer의 serializeUser(user, done) 메소드를 호출합니다.
      • serializeUser 메소드는 user 객체(인증된 사용자 정보)를 받아, 세션에 저장할 최소한의 정보 (보통 user.id와 같은 고유 식별자)를 결정합니다.
      • done(null, userId)를 호출하여 선택된 식별자(userId)를 Passport에 전달합니다.
    5. 세션 데이터 저장 (express-session & Session Store)
      • Passport는 serializeUser로부터 받은 userId를 req.session.passport.user에 저장합니다. (예: req.session.passport.user = 1)
      • express-session 미들웨어는 req.session 객체가 변경되었음을 감지합니다.
      • 변경된 req.session 객체 전체 (여기에는 passport: { user: 1 }이 포함됨)를 **세션 스토어(PostgreSQL)**에 저장합니다. 이때 새로운 세션이라면 고유한 세션 ID가 생성되어 이 세션 ID와 함께 데이터가 저장됩니다.
    6. 세션 ID를 클라이언트에 전송 (서버 → 클라이언트)
      • express-session 미들웨어는 생성된/기존의 세션 ID를 사용하여 Set-Cookie HTTP 응답 헤더를 만듭니다.
      • 이 헤더에는 세션 쿠키 정보(예: connect.sid=세션ID값; Path=/; HttpOnly; Max-Age=...)가 포함됩니다.
      • 서버는 이 응답을 클라이언트에게 보냅니다.
    7. 클라이언트 쿠키 저장
      • 웹 브라우저는 Set-Cookie 헤더를 보고 해당 쿠키를 자동으로 저장합니다. Postman과 같은 도구도 쿠키를 관리합니다.

    5. 인증된 사용자의 후속 요청 처리 과정

    1. 클라이언트, 세션 쿠키와 함께 요청 전송 (클라이언트 → 서버)
      • 사용자가 로그인 후 다른 페이지로 이동하거나 보호된 API에 접근할 때, 브라우저는 저장된 세션 쿠키(connect.sid=...)를 Cookie HTTP 요청 헤더에 자동으로 포함시켜 서버로 보냅니다.
    2. 서버, 요청 및 세션 ID 수신 (NestJS/express-session)
      • 요청이 서버에 도달하면, 가장 먼저 실행되는 미들웨어 중 하나인 express-session이 Cookie 헤더에서 세션 ID를 추출합니다.
    3. 세션 데이터 조회 (express-session & Session Store)
      • express-session은 추출한 세션 ID를 사용하여 세션 스토어(PostgreSQL)에서 해당 세션 데이터를 조회합니다.
      • 세션 데이터를 찾으면, 그 내용을 req.session 객체에 복원합니다. (여기에는 passport: { user: 저장된ID }가 포함됩니다.)
    4. 세션 역직렬화 (Deserialization - Passport)
      • passport.session() 미들웨어 (express-session 이후에 실행됨)가 req.session.passport.user에 저장된 식별자(예: userId)를 확인합니다.
      • 이 식별자를 사용하여 등록된 SessionSerializer의 deserializeUser(userId, done) 메소드를 호출합니다.
      • deserializeUser 메소드는 전달받은 userId를 사용하여 데이터베이스(User 테이블)에서 전체 사용자 정보를 조회합니다 (예: userRepository.findOne({ where: { id: userId } })).
      • 조회된 user 객체(보통 비밀번호 제외)를 done(null, user)를 통해 Passport에 전달합니다. (사용자를 찾지 못하거나 오류 발생 시 done(err) 또는 done(null, null) 호출)
    5. req.user 객체 채우기 (Passport)
      • Passport는 deserializeUser로부터 받은 완전한 user 객체를 req.user에 할당합니다. 이제 애플리케이션의 모든 요청 핸들러나 가드에서 req.user를 통해 현재 로그인된 사용자의 정보에 접근할 수 있습니다.
    6. 보호된 리소스 접근 (NestJS Guards & Controllers)
      • @UseGuards(AuthenticatedGuard)와 같은 가드는 req.isAuthenticated() 메소드(Passport가 제공)나 req.user 객체의 존재 여부를 확인하여 사용자가 인증되었는지 판별합니다.
      • 인증된 사용자라면 요청 처리를 허용하고, 그렇지 않다면 접근을 거부합니다 (예: 401 Unauthorized 응답).
      • 컨트롤러 메소드 내에서는 req.user를 통해 사용자별 로직을 수행할 수 있습니다.

    6. 로그아웃 처리 과정

    1. 클라이언트 로그아웃 요청 (클라이언트 → 서버)
      • 사용자가 로그아웃 버튼을 클릭하면, 클라이언트는 서버의 로그아웃 API 엔드포인트(예: POST /auth/logout)로 요청을 보냅니다.
    2. 서버 세션 처리 (NestJS/Passport/express-session)
      • req.logout(callback): Passport가 제공하는 함수로, 호출 시 req.user를 제거하고 Passport의 로그인 세션 정보를 정리합니다. (NestJS 최신 버전에서는 콜백이 필수가 되었습니다.)
      • req.session.destroy(callback): express-session이 제공하는 함수로, 세션 스토어에서 해당 세션 데이터를 완전히 삭제합니다.
      • res.clearCookie('connect.sid'): (선택적이지만 권장) 클라이언트의 브라우저에서 세션 쿠키를 삭제하도록 지시합니다.
      • 서버는 로그아웃 성공 응답을 클라이언트에게 보냅니다.

    7. 관련된 주요 NestJS/Passport 구성 요소 요약

    • express-session: 핵심 세션 관리 미들웨어. 세션 생성, ID 발급, 쿠키 설정, 세션 스토어 연동 등을 담당.
    • connect-pg-simple: express-session이 PostgreSQL을 세션 스토어로 사용하도록 하는 어댑터.
    • passport: 인증 로직을 처리하는 프레임워크. 다양한 인증 전략(Strategy)을 지원.
    • passport-local: 아이디/비밀번호 기반 인증을 위한 Passport 전략.
    • PassportModule.register({ session: true }): NestJS에서 Passport가 세션을 사용하도록 설정.
    • AuthGuard('local') (또는 커스텀 LocalAuthGuard): 'local' 전략을 실행시켜 사용자 인증을 시도하는 가드.
    • LocalStrategy: passport-local 전략의 실제 구현부. validate 메소드에서 사용자 자격 증명을 검증.
    • SessionSerializer: serializeUser (세션에 저장할 정보 선택)와 deserializeUser (세션 정보로 전체 사용자 객체 복원) 메소드를 구현.
    • AuthenticatedGuard (또는 유사한 커스텀 가드): 특정 라우트에 접근하기 전에 사용자가 인증되었는지 확인하는 가드.

     

    반응형

    댓글

Designed by Tistory.