Nest.js를 배워보자/7. NestJS 인증 시스템 구축 — JWT & Passport 실전

NestJS 인증 시스템 구축: JWT & Passport 실전

_Blue_Sky_ 2025. 12. 2. 19:52
728x90


1. 🔑 인증 시스템의 핵심 구성 요소

NestJS에서 안전하고 확장 가능한 인증 시스템을 구축하기 위해 주로 JWT (JSON Web Token) 표준과 Passport 모듈을 사용합니다.

  • Passport: Node.js의 미들웨어로, 다양한 인증 메커니즘을 플러그인 형태로 지원하는 프레임워크입니다. NestJS는 @nestjs/passport 모듈을 통해 이를 통합합니다.
  • JWT: 사용자 정보를 담고 있는 서명된 토큰입니다. 이 토큰은 클라이언트에 저장되며, 이후 요청 시 서버에 전송되어 사용자 인증에 사용됩니다.
  • Guard: JWT의 유효성을 검사하고 사용자에게 접근 권한이 있는지 확인하는 NestJS의 핵심 컴포넌트입니다.

2. 📝 Passport Strategy (전략) 정의

Passport는 인증 방식을 전략(Strategy)이라고 부릅니다. 일반적인 인증 과정에는 두 가지 주요 전략이 사용됩니다.

2.1. Local Strategy (로그인)

사용자 이름(이메일)과 비밀번호를 검증하여 사용자를 인증하는 전략입니다. 이는 로그인 시점에만 사용됩니다.

  1. 사용자 검증: 클라이언트가 전송한 자격 증명(Credential)을 데이터베이스에 저장된 값과 비교합니다.
  2. 토큰 생성: 검증에 성공하면, AuthService를 통해 사용자 정보를 담은 JWT를 생성합니다.
// local.strategy.ts (예시)
import { Strategy } from 'passport-local';

@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
  constructor(private authService: AuthService) {
    super();
  }

  // 💡 Passport가 호출하는 검증 함수
  async validate(email: string, pass: string): Promise<any> {
    const user = await this.authService.validateUser(email, pass);
    if (!user) {
      throw new UnauthorizedException('아이디 또는 비밀번호가 일치하지 않습니다.');
    }
    // 💡 JWT Payload에 담길 사용자 정보 반환
    return user; 
  }
}
728x90

2.2. JWT Strategy (토큰 검증)

로그인 후 발생하는 모든 요청에 대해 JWT 토큰을 검증하는 전략입니다.

  1. 토큰 추출: 요청 헤더에서 JWT를 추출합니다.
  2. 서명 검증: JWT의 서명(Signature)이 유효한지 확인합니다.
  3. Payload 반환: 유효성 검증이 완료되면, 토큰의 내용(Payload)을 추출하여 요청 객체에 담습니다.
// jwt.strategy.ts (예시)
import { Strategy, ExtractJwt } from 'passport-jwt';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), // 💡 Bearer 토큰에서 JWT 추출
      secretOrKey: process.env.JWT_SECRET, // JWT 서명 검증 키
    });
  }

  // 💡 토큰이 유효할 때 호출됨
  async validate(payload: any) {
    // 💡 payload를 request.user에 주입하여 컨트롤러에서 사용 가능하게 함
    return { userId: payload.sub, email: payload.email }; 
  }
}

3. 🛡️ Guard를 이용한 라우트 보호

Passport 전략이 정의되면, 이를 NestJS의 Guard로 변환하여 라우트에 적용합니다.

3.1. Local Guard (로그인 시점)

로그인 요청이 들어오면 AuthGuard('local')이 LocalStrategy를 호출합니다.

// auth.controller.ts
import { Post, UseGuards, Request } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Controller('auth')
export class AuthController {
  
  // 💡 LocalStrategy를 실행하도록 지시하는 Guard
  @UseGuards(AuthGuard('local')) 
  @Post('login')
  async login(@Request() req) {
    // LocalStrategy의 validate가 성공하면 req.user에 사용자 정보가 담깁니다.
    return this.authService.login(req.user); // JWT 토큰 생성 및 반환 로직
  }
}

 

728x90

3.2. JWT Guard (모든 인증 필요 요청)

인증이 필요한 모든 API 엔드포인트에 AuthGuard('jwt')를 적용하여 JWT의 유효성을 검증합니다.

// users.controller.ts
import { Get, UseGuards, Request } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Controller('users')
export class UsersController {
  
  // 💡 JwtStrategy를 실행하도록 지시하는 Guard
  @UseGuards(AuthGuard('jwt')) 
  @Get('profile')
  getProfile(@Request() req) {
    // JWT가 유효하면 req.user에는 JwtStrategy에서 반환한 Payload 정보가 담겨 있습니다.
    return req.user; 
  }
}

4. 🥇 NestJS 인증 시스템의 장점

  • 명확한 책임 분리:
    • Strategy: 사용자 검증 및 토큰 처리 로직만 담당.
    • Guard: 해당 라우트 접근 권한을 허용/거부하는 역할만 담당.
    • Controller: 인증/인가에 신경 쓰지 않고 비즈니스 로직만 수행.
  • 플러그인 방식: Passport 덕분에 JWT 외에도 Google, Facebook 등 다른 OAuth 인증 방식을 쉽게 추가할 수 있습니다.
728x90