ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • NestJS_온라인 공연 예매 서비스 프로젝트 4편_유저기능 구현(회원가입 및 로그인[下])
    TIL (Today I Learned) 2023. 12. 27. 22:12

    # Nest JS 프로젝트 유저 기능 구현을 시작에 앞서

    이전 포스팅에서는 회원가입 및 로그인 [상]에서 user 모듈, 서비스, 컨트롤러를 기본적으로 구현했습니다. 더불어, Enum을 활용하여 user와 admin을 분리하고, user.entity를 세심히 작성했습니다. 또한, user.controller를 효과적으로 활용하기 위해 필요한 DTO를 생성하고, userInfo라는 커스텀 데코레이터도 구현했습니다.

     

    이전 단계에 대한 궁금증이 남아 계신 분들은 아래의 포스팅을 차례로 살펴보시길 권장합니다.

     

    NestJS_온라인 공연 예매 서비스 프로젝트 1편_기본 세팅

    # Nest JS 프로젝트를 시작하며 TypeScript와 NestJS를 활용한 온라인 공연 예매 서비스 프로젝트를 자세하게 기록하는 포스팅을 시작하고자 합니다. 지금까지 다양한 과제를 수행해왔지만, 아마도 이

    k0zdevel.tistory.com

     

    NestJS_온라인 공연 예매 서비스 프로젝트 2편_프로젝트 필수 기능 구현

    # Nest JS 프로젝트 본격적으로 시작에 앞서 지난 포스팅에 이어 두 번째 이야기를 시작하겠습니다. 이전 글에서는 프로젝트의 기초 세팅에 대해 다뤘습니다. 만약 그 내용에 대해 궁금하시거나

    k0zdevel.tistory.com

     

    NestJS_온라인 공연 예매 서비스 프로젝트 3편_유저기능 구현(회원가입 및 로그인[上])

    # Nest JS 프로젝트 유저 기능 구현을 시작에 앞서 지난 글에서는 엄격한 DTO 다루기와 ValidationPipe의 활용, TypeORM을 사용한 데이터베이스 연결, 그리고 Auth 모듈을 생성해 인증 및 인가 기능을 구현

    k0zdevel.tistory.com

     

    NESTJS_예외처리의 중요성과 종류

    # 예외처리의 중요성 Nest.js에서는 예외를 처리하고 사용자에게 적절한 응답을 제공하는 것이 중요합니다. 이를 통해 애플리케이션의 안정성을 유지하고 클라이언트와의 통신을 원활하게 할 수

    k0zdevel.tistory.com


    # Nest JS_user.controller.ts 작업

    UserController는 주로 HTTP 요청을 처리하고 해당 요청에 따른 사용자 관련 동작을 수행하는 Nest.js의 컨트롤러 클래스입니다. 컨트롤러는 클라이언트로부터의 HTTP 요청을 받아들이고, 해당 요청에 따라 비즈니스 로직을 호출하거나 다른 모듈의 서비스를 사용하여 요청을 처리합니다.

     

    앞서 작성한 포스트에서 사용자의 컨트롤러 작업 준비를 마쳤습니다. 이번에는 Passport 라이브러리와 직접 제작한 JwtStrategy를 활용하여 안전하고 효과적인 사용자 등록 및 로그인 코드를 작성해보겠습니다. 이를 통해 Passport라이브러리와 직접 만든 JwtStrategy를 사용하여 JWT 인증이 된 사용자에게만 호출이 허용되는 getEmailAPI를 구현할 수 있습니다. 

     

    1. HTTP 엔드포인트 정의

    사용자 관리와 관련된 다양한 동작을 수행하는 엔드포인트들을 정의합니다. 예를 들어, 사용자 생성, 조회, 수정, 삭제에 대한 엔드포인트를 정의할 수 있습니다.

     

    2. HTTP 요청 처리

    클라이언트로부터의 HTTP 요청을 받아들이고, 해당 요청에 대한 처리를 수행합니다.

     

    3. 서비스와의 상호 작용

    UserController는 주로 UserService와 같은 서비스 클래스를 호출하여 사용자와 관련된 비즈니스 로직을 처리합니다. 서비스는 데이터베이스와 상호 작용하거나 다양한 비즈니스 로직을 담당하므로, 컨트롤러는 이를 활용하여 요청을 처리합니다.

     

    4. 요청과 응답 처리

    클라이언트로부터의 HTTP 요청을 받아들이고, 서비스로부터 받은 데이터를 기반으로 클라이언트에게 응답을 생성합니다. 이때, Nest.js에서는 데코레이터를 사용하여 엔드포인트에 대한 요청과 응답을 정의할 수 있습니다.

     

    ***user.controller 코드 작성***

    ***user.controller 코드 해석***

    import { UserInfo } from 'src/utils/userInfo.decorator'; // 'src/utils/userInfo.decorator'에서 UserInfo 데코레이터를 가져옵니다.
    import { Body, Controller, Get, Post, UseGuards } from '@nestjs/common'; // NestJS에서 제공하는 여러 데코레이터와 모듈을 가져옵니다.
    import { AuthGuard } from '@nestjs/passport'; // Passport에서 제공하는 AuthGuard를 가져옵니다.
    import { LoginDto } from './dto/login.dto'; // 로그인과 관련된 데이터 전송 객체(DTO)를 가져옵니다.
    import { User } from './entities/user.entity'; // 사용자 엔터티를 나타내는 클래스를 가져옵니다.
    import { UserService } from './user.service';  // 사용자 서비스를 가져옵니다.
    
    // '/auth' 경로에 대한 컨트롤러를 정의합니다.
    @Controller('auth')
    export class UserController {
     // UserService 인스턴스를 주입받아 생성자에서 사용합니다.
      constructor(private readonly userService: UserService) {}  
    
      // 회원가입을 처리하는 POST 엔드포인트 '/auth/register'를 정의합니다.
      @Post('register')
      async register(@Body() loginDto: LoginDto) {
       // 주입된 userService의 register 메서드를 호출하여 회원가입을 처리하고 결과를 반환합니다.
        return await this.userService.register(loginDto.email, loginDto.password); 
      }
    
      // 로그인을 처리하는 POST 엔드포인트 '/auth/login'를 정의합니다.
      @Post('login')
      async login(@Body() loginDto: LoginDto) {
       // 주입된 userService의 login 메서드를 호출하여 로그인을 처리하고 결과를 반환합니다.
        return await this.userService.login(loginDto.email, loginDto.password);
      }
    
      // JWT 인증을 사용하여 보호된 GET 엔드포인트 '/auth/email'를 정의합니다.
      @UseGuards(AuthGuard('jwt'))
      @Get('email')
      // @UserInfo() 데코레이터를 사용하여 사용자 정보를 추출하고 getEmail 메서드에 전달합니다.
      getEmail(@UserInfo() user: User) {  
       // 추출된 사용자 이메일 정보를 응답으로 반환합니다.
       return { email: user.email };  // 추출된 사용자 이메일 정보를 응답으로 반환합니다.
      }
    }

     

    1. register 함수

    • POST 메서드로 '/auth/register' 엔드포인트를 처리합니다.
    • @Body() 데코레이터를 사용하여 요청 본문에서 LoginDto 형식의 데이터를 추출합니다.
    • userService.register를 호출하여 사용자를 등록하고 결과를 반환합니다.

    2. login 함수

    • POST 메서드로 '/auth/login' 엔드포인트를 처리합니다.
    • @Body() 데코레이터를 사용하여 요청 본문에서 LoginDto 형식의 데이터를 추출합니다.
    • userService.login을 호출하여 사용자를 로그인하고 결과를 반환합니다.

    3. getEmail 함수

    • GET 메서드로 '/auth/email' 엔드포인트를 처리합니다.
    • @UseGuards(AuthGuard('jwt')) 데코레이터를 사용하여 해당 API를 호출할 때 JWT로 인증된 사용자만 허용됩니다.
    • @UserInfo() 데코레이터를 사용하여 JWT에서 추출된 사용자 정보를 user 매개변수로 받습니다.
    • JWT를 통해 인증된 사용자의 이메일 정보를 반환합니다.

    4. JWT 인증과 AuthGuard 함수

    • @UseGuards(AuthGuard('jwt')) 데코레이터는 JWT로 인증된 사용자에게만 해당 API를 호출할 수 있도록 합니다.
    • AuthGuard는 Nest.js에서 제공하는 Passport 라이브러리를 기반으로 한 인증 가드입니다.
    • 이 가드는 특정 인증 전략을 사용하여 요청이 처리되기 전에 인증을 수행합니다.
    • 코드에서는 Passport를 활용하여 직접 만든 JwtStrategy를 사용하여 JWT로 인증된 사용자만이 getEmail API를 호출할 수 있도록 설정되어 있습니다.
    • 따라서 로그인하지 않은 유저는 해당 API를 호출할 수 없습니다.

    # Nest JS_user.service.ts 작업

    UserService는 주로 사용자와 관련된 비즈니스 로직을 처리하는 서비스 클래스입니다. Nest.js에서 서비스 클래스는 애플리케이션의 비즈니스 로직을 담당하며, 주로 컨트롤러와 데이터베이스 사이에서 중재자 역할을 합니다. 

     

    1. 사용자 생성(Create)

    새로운 사용자를 생성하고 데이터베이스에 저장합니다.

     

    2. 사용자 조회(Read)

    사용자의 정보를 조회하고 필요한 경우 특정 사용자의 상세 정보를 반환합니다.

     

    3. 사용자 업데이트(Update)

    사용자의 정보를 수정하고 업데이트된 정보를 데이터베이스에 반영합니다.

     

    4. 사용자 삭제(Delete)

    특정 사용자를 식별하여 삭제하고 해당 정보를 데이터베이스에서 제거합니다.

     

    5. 사용자 검색(Search)

    특정 조건에 따라 사용자를 검색하고 필요한 정보를 반환합니다.

     

    6. 비즈니스 로직 처리

    필요한 경우 사용자와 관련된 비즈니스 로직을 처리합니다. 예를 들어, 사용자 간의 상호 작용이나 권한 관리와 같은 기능을 수행할 수 있습니다.

     

    7. 에러 처리

    데이터베이스 작업이나 비즈니스 로직 수행 중 발생하는 오류를 적절히 처리하고 예외를 관리합니다.

     

    UserService는 컨트롤러에서 받은 요청을 처리하고, 필요에 따라 데이터베이스와 상호 작용하여 비즈니스 로직을 수행합니다. 이는 코드를 모듈화하고 재사용 가능한 서비스로 분리함으로써 유지보수성을 향상시키고 테스트가 쉽도록 도와줍니다.

    ***user.service 코드 작성***

    ***user.service 코드 해석***

    import { compare, hash } from 'bcrypt';  // bcrypt 라이브러리에서 비밀번호 해싱과 비교 함수 import
    import _ from 'lodash';  // lodash 라이브러리 사용
    import { Repository } from 'typeorm';  // TypeORM 라이브러리에서 Repository import
    
    import {
      BadRequestException,
      ConflictException,
      Injectable,
      InternalServerErrorException,
      UnauthorizedException,
    } from '@nestjs/common';  // Nest.js에서 사용되는 예외 클래스들 import
    import { JwtService } from '@nestjs/jwt';  // JWT 서비스 import
    import { InjectRepository } from '@nestjs/typeorm';  // TypeORM에서 Repository를 주입하는 데코레이터 import
    
    import { User } from './entities/user.entity';  // User 엔터티 import
    
    @Injectable()
    export class UserService {
      constructor(
        @InjectRepository(User)
        private userRepository: Repository<User>,  // User 엔터티의 Repository 주입
        private readonly jwtService: JwtService,   // JWT 서비스 주입
      ) {}
    
      // 회원가입
      async register(email: string, password: string) {
        try {
          // 이메일 형식 정규식
          const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    
          if (!email) {
            throw new BadRequestException('이메일 입력이 필요합니다.');  // 이메일이 없으면 BadRequestException 발생
          }
    
          // 이메일 형식 검증
          if (!emailRegex.test(email)) {
            throw new BadRequestException('올바르지 않은 이메일 형식입니다.');  // 이메일 형식이 아니면 BadRequestException 발생
          }
    
          const existingUser = await this.findByEmail(email);
          if (existingUser) {
            throw new ConflictException('이미 등록된 이메일입니다.');  // 이미 등록된 이메일이면 ConflictException 발생
          }
    
          if (!password) {
            throw new BadRequestException('비밀번호 입력이 필요합니다.');  // 비밀번호가 없으면 BadRequestException 발생
          }
    
          if (password.length < 6) {
            throw new BadRequestException('비밀번호는 최소 6자 이상이어야 합니다.');  // 비밀번호가 짧으면 BadRequestException 발생
          }
    
          // 비밀번호 해싱 및 사용자 저장
          const hashedPassword = await hash(password, 10);
          await this.userRepository.save({
            email,
            password: hashedPassword,
          });
        } catch (error) {
          throw new InternalServerErrorException(
            '예상치 못한 에러가 발생했습니다. 관리자에게 문의하세요.',
          );  // 다른 예외가 발생하면 InternalServerErrorException으로 감싸서 처리
        }
      }
    
      // 로그인
      async login(email: string, password: string) {
        try {
          if (!email) {
            throw new BadRequestException('이메일 입력이 필요합니다.');  // 이메일이 없으면 BadRequestException 발생
          }
    
          const user = await this.userRepository.findOne({
            select: ['id', 'email', 'password'],
            where: { email },
          });
    
          if (_.isNil(user)) {
            throw new UnauthorizedException(
              '이메일 또는 비밀번호가 일치하는 인증 정보가 없습니다.',
            );  // 사용자가 없으면 UnauthorizedException 발생
          }
    
          if (!password) {
            throw new BadRequestException('비밀번호 입력이 필요합니다.');  // 비밀번호가 없으면 BadRequestException 발생
          }
    
          if (!(await compare(password, user.password))) {
            throw new UnauthorizedException(
              '이메일 또는 비밀번호가 일치하는 정보가 없습니다.',
            );  // 비밀번호가 일치하지 않으면 UnauthorizedException 발생
          }
    
          // JWT 토큰 발급
          const payload = { email, sub: user.id };
          return {
            access_token: this.jwtService.sign(payload),
          };
        } catch (error) {
          throw new InternalServerErrorException(
            '예상치 못한 에러가 발생했습니다. 관리자에게 문의하세요.',
          );  // 다른 예외가 발생하면 InternalServerErrorException으로 감싸서 처리
        }
      }
    
      // 이메일로 사용자 조회
      async findByEmail(email: string) {
        return await this.userRepository.findOneBy({ email });  // 주어진 이메일로 사용자 조회
      }
    }

     

    1. register 메서드

    • register 메서드는 사용자를 등록합니다.
    • 이메일 형식과 중복 여부, 비밀번호의 존재 여부 및 길이를 확인합니다.
    • 이메일 형식이나 비밀번호 길이 등의 조건에 어긋나면 BadRequestException을 발생시킵니다.
    • 에러가 발생하면 InternalServerErrorException으로 감싸서 예외를 처리합니다.
    • bcrypt 패키지를 활용하여 비밀번호를 해싱하여 저장합니다.

    2. login 메서드

    • login 메서드는 사용자를 로그인시킵니다.
    • 이메일, 사용자 확인, 비밀번호 존재 여부와 일치 여부를 확인합니다.
    • 어긋나는 조건이 있으면 BadRequestException 또는 UnauthorizedException을 발생시킵니다.
    • 로그인에 성공하면  bcrypt의 compare 함수를 통해서 비밀번호 비교 후 일치 시에 JWT 토큰을 발급하여 반환합니다.
    • 에러가 발생하면 InternalServerErrorException으로 감싸서 예외를 처리합니다.

    3. findByEmail 메서드

    • findByEmail 메서드는 주어진 이메일로 사용자를 조회합니다.
    • 데이터베이스에서 해당 이메일을 가진 사용자를 찾아 반환합니다.

    4. 그외 추가설명

    • 코드에서 lodash의 _.isNil 함수를 사용하여 user가 null 또는 undefined인지 확인합니다.
    • 에러가 발생할 땐, InternalServerErrorException으로 예외 처리하고 사용자가 관리자에게 문의하도록 안내합니다.

    # Nest JS_user.module.ts 작업

    다음으로 user.module 코드를 작성해보겠습니다. user.module은 주로 사용자와 관련된 기능을 모듈화하여 제공하는데 사용됩니다. 모듈은 애플리케이션을 구성하는 주요 구성 요소 중 하나이며, 특정 기능이나 도메인을 담당하는 논리적인 단위로 분리됩니다. 

     

    1. 모듈화된 기능 제공

    user.module은 사용자 관리와 관련된 모든 기능을 포함합니다. 이는 사용자 생성, 조회, 수정, 삭제 등과 같은 사용자 관리에 필요한 기능들을 모듈 내부에서 관리합니다.

     

    2. 의존성 관리

    모듈은 해당 기능을 구현하기 위해 필요한 의존성을 관리합니다. 위 예시에서는 TypeORM을 사용하여 데이터베이스와 상호 작용하기 위한 의존성을 관리하고 있습니다.

     

    3. 컨트롤러와 서비스 제공

    user.module은 해당 기능을 제공하기 위해 컨트롤러와 서비스를 포함합니다. 컨트롤러는 HTTP 요청을 처리하고 서비스는 비즈니스 로직을 수행합니다.

     

    4. 모듈 간 통신과 재사용

    Nest.js에서 모듈은 서로 통신하고 재사용될 수 있습니다. 다른 모듈에서 UserModule을 import하여 사용할 수 있으며, 필요한 기능을 손쉽게 재사용할 수 있습니다.

     

    5. 데이터베이스 연결 관리

    user.module은 TypeORM을 사용하여 사용자 엔터티와 관련된 데이터베이스 연결을 설정하고, 필요한 경우 해당 데이터베이스 테이블을 생성하거나 관리할 수 있습니다.

     

    6. 전역적인 애플리케이션 스코프에서 서비스 제공

    providers 배열에 등록된 UserService를 통해 해당 모듈에서 정의한 서비스를 전역적으로 사용할 수 있습니다. 다른 모듈에서 이 서비스를 주입받아 사용할 수 있습니다.

     

    요약하면, user.module은 사용자와 관련된 모든 것을 담당하는 모듈로, 이를 통해 애플리케이션을 모듈 단위로 나누고 관리할 수 있습니다.

     

    ***user.module 코드 작성***

    ***user.module 코드 해석***

    import { Module } from '@nestjs/common'; // Nest.js의 Module 데코레이터를 사용하여 모듈을 정의합니다.
    import { ConfigService } from '@nestjs/config'; // 환경 변수를 사용하기 위해 ConfigService를 Nest.js에서 제공하는 모듈에서 가져옵니다.
    import { JwtModule } from '@nestjs/jwt'; // JWT(JSON Web Token) 기능을 제공하는 JwtModule을 Nest.js에서 제공하는 모듈에서 가져옵니다.
    import { TypeOrmModule } from '@nestjs/typeorm'; // TypeORM을 사용하여 데이터베이스와 상호 작용하기 위해 TypeOrmModule을 Nest.js에서 제공하는 모듈에서 가져옵니다.
    
    import { User } from './entities/user.entity'; // User 엔터티를 정의한 파일에서 User 엔터티를 가져옵니다.
    import { UserController } from './user.controller'; // User 컨트롤러 클래스를 가져옵니다.
    import { UserService } from './user.service'; // User 서비스 클래스를 가져옵니다.
    
    // Nest.js의 Module 데코레이터를 사용하여 UserModule을 정의합니다.
    @Module({
      // JwtModule.registerAsync를 사용하여 비동기적으로 JWT 설정을 가져오고 사용합니다.
      imports: [
        JwtModule.registerAsync({
          // useFactory 함수를 사용하여 JWT 설정을 동적으로 생성합니다.
          useFactory: (config: ConfigService) => ({
            secret: config.get<string>('JWT_SECRET_KEY'),
          }),
          // useFactory 함수에 필요한 의존성을 주입합니다. 여기서는 ConfigService가 필요합니다.
          inject: [ConfigService],
        }),
        
        // TypeOrmModule.forFeature를 사용하여 User 엔터티를 해당 모듈에 추가합니다.
        TypeOrmModule.forFeature([User]),
      ],
      
      // UserService를 제공하는 프로바이더를 등록합니다.
      providers: [UserService],
      
      // UserController를 등록하여 HTTP 요청을 처리할 수 있도록 합니다.
      controllers: [UserController],
      
      // UserService를 다른 모듈에서 사용할 수 있도록 내보냅니다.
      exports: [UserService],
    })
    export class UserModule {}

     

    1. @Module 데코레이터

    • @Module 데코레이터는 Nest.js에서 모듈을 정의하는 주요 수단입니다. 이 데코레이터 안에는 해당 모듈에 필요한 구성 요소들을 등록합니다.

    2. 모듈의 구성 요소

    • imports: 다른 모듈을 가져와 현재 모듈에 통합합니다. 이 코드에서는 JwtModule과 TypeOrmModule을 가져와 사용하고 있습니다.
    • providers: 현재 모듈에서 사용할 서비스나 프로바이더를 등록합니다. 여기서는 UserService를 등록하고 있습니다.
    • controllers: 현재 모듈에서 사용할 컨트롤러를 등록합니다. 여기서는 UserController를 등록하고 있습니다.
    • exports: 다른 모듈에서 사용할 수 있도록 현재 모듈에서 제공하는 기능을 명시적으로 내보냅니다. 여기서는 UserService를 다른 모듈에서 사용할 수 있도록 내보냅니다.

    3. JwtModule.registerAsync

    • 비동기적으로 JWT 설정을 가져오는 부분입니다. useFactory 함수를 사용하여 동적으로 JWT 설정을 생성하고, 이때 ConfigService를 주입받아 환경 변수에서 JWT 시크릿 키를 가져와 사용합니다.

    4. TypeOrmModule.forFeature

    • TypeORM을 사용하여 데이터베이스와 상호 작용하기 위해 User 엔터티를 현재 모듈에 추가하는 부분입니다.

    5. UserService와 UserController

    • UserService는 사용자와 관련된 비즈니스 로직을 처리하는 서비스입니다.
    • UserController는 HTTP 요청을 처리하고 해당 요청에 대한 비즈니스 로직을 UserService를 통해 호출하는 컨트롤러입니다.

    6. 모듈의 재사용성

    • exports를 통해 UserService를 다른 모듈에서 사용할 수 있도록 내보내었습니다. 이렇게 함으로써 해당 모듈을 다른 모듈에서 재사용할 수 있습니다.

    7. 환경 변수 사용

    • ConfigService를 사용하여 환경 변수를 관리하고 있습니다. 환경 변수를 사용함으로써 설정을 동적으로 관리할 수 있습니다.

    # Nest JS_app.module.ts 코드 수정하기

    AuthModule과 UserModule을 import에 추가하고 typeOrmModuleOptions의 entities 속성에 User entity를 추가해야합니다. 이를 통해 각 모듈 간의 의존성을 설정하고, TypeORM이 데이터베이스와 어떤 엔터티를 사용할지 명시하였습니다.

    ***app.module 코드 수정***

    ***app.module 수정코드***

    // app.module.ts
    
    import Joi from 'joi';
    import { SnakeNamingStrategy } from 'typeorm-naming-strategies';
    
    import { Module } from '@nestjs/common';
    import { ConfigModule, ConfigService } from '@nestjs/config';
    import { TypeOrmModule, TypeOrmModuleOptions } from '@nestjs/typeorm';
    
    import { AuthModule } from './auth/auth.module';
    import { User } from './user/entities/user.entity';
    import { UserModule } from './user/user.module';
    
    // TypeORM 설정 옵션을 동적으로 생성하는 함수
    const typeOrmModuleOptions = {
      useFactory: async (
        configService: ConfigService,
      ): Promise<TypeOrmModuleOptions> => ({
        namingStrategy: new SnakeNamingStrategy(), // Snake 케이스 네이밍 전략 사용
        type: 'mysql',
        username: configService.get('DB_USERNAME'),
        password: configService.get('DB_PASSWORD'),
        host: configService.get('DB_HOST'),
        port: configService.get('DB_PORT'),
        database: configService.get('DB_NAME'),
        entities: [User], // 사용할 엔티티 목록
        synchronize: configService.get('DB_SYNC'), // 데이터베이스 스키마 자동 동기화 여부
        logging: true, // 쿼리 로깅 여부
      }),
      inject: [ConfigService], // ConfigService 주입
    };
    
    @Module({
      imports: [
        // Nest.js ConfigModule 설정
        ConfigModule.forRoot({
          isGlobal: true, // 전역으로 설정
          validationSchema: Joi.object({
            JWT_SECRET_KEY: Joi.string().required(),
            DB_USERNAME: Joi.string().required(),
            DB_PASSWORD: Joi.string().required(),
            DB_HOST: Joi.string().required(),
            DB_PORT: Joi.number().required(),
            DB_NAME: Joi.string().required(),
            DB_SYNC: Joi.boolean().required(),
          }),
        }),
    
        // TypeORM 설정을 비동기적으로 가져와서 사용
        TypeOrmModule.forRootAsync(typeOrmModuleOptions),
    
        // AuthModule과 UserModule 추가
        AuthModule,
        UserModule,
      ],
      controllers: [], // 컨트롤러
      providers: [], // 프로바이더
    })
    export class AppModule {}

    # Nest JS_ jwt.strategt.ts 코드 수정하기

    ***jwt.strategt 코드수정***

    ***jwt.strategt 수정코드***

    import _ from 'lodash'; // lodash 라이브러리를 _로 import
    import { ExtractJwt, Strategy } from 'passport-jwt'; // passport-jwt에서 필요한 클래스들을 import
    import { UserService } from 'src/user/user.service'; // UserService를 import
    
    import { Injectable, NotFoundException } from '@nestjs/common'; // Nest.js에서 사용하는 데코레이터와 예외 클래스를 import
    import { ConfigService } from '@nestjs/config'; // ConfigService를 import
    import { PassportStrategy } from '@nestjs/passport'; // PassportStrategy를 import
    
    @Injectable() // Nest.js에서 제공하는 Injectable 데코레이터를 사용하여 서비스를 주입할 수 있도록 설정
    export class JwtStrategy extends PassportStrategy(Strategy) {
      constructor(
        private readonly userService: UserService, // UserService 주입
        private readonly configService: ConfigService, // ConfigService 주입
      ) {
        // Passport 전략(Strategy)을 초기화하는 constructor
        super({
          jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), // JWT 토큰을 추출하는 방법 설정 (Bearer 토큰)
          ignoreExpiration: false, // 만료 기간 검증 여부
          secretOrKey: configService.get('JWT_SECRET_KEY'), // JWT 토큰을 해독하는 데 사용되는 비밀 키 설정
        });
      }
    
      async validate(payload: any) {
        // Passport 전략에서 필요한 validate 메서드를 구현
        const user = await this.userService.findByEmail(payload.email); // 페이로드에서 이메일을 추출하고 UserService를 사용하여 사용자 검색
        if (_.isNil(user)) {
          // lodash의 isNil을 사용하여 사용자가 없는 경우 예외 처리
          throw new NotFoundException('해당하는 사용자를 찾을 수 없습니다.');
        }
    
        return user; // 사용자가 존재하면 해당 사용자를 반환
      }
    }

    1. 라이브러리 및 모듈 Import

    • lodash와 Passport-JWT에서 필요한 클래스 및 모듈들을 import합니다.

    2. Injectable 데코레이터

    • @Injectable()을 사용하여 Nest.js의 의존성 주입을 활성화하고, 서비스로써 해당 클래스를 사용할 수 있도록 설정합니다.

    3. 클래스 정의

    • JwtStrategy 클래스를 정의하며, PassportStrategy 클래스를 상속합니다.

    4. Constructor

    • 클래스의 생성자에서 UserService와 ConfigService를 주입받습니다.
    • Passport 전략을 초기화하고, JWT 관련 설정을 제공합니다.

    5. validate 메서드

    • Passport에서 요구하는 validate 메서드를 구현합니다.
    • JWT 토큰의 페이로드에서 이메일을 추출하고, UserService를 사용하여 해당 이메일을 가진 사용자를 검색합니다.
    • 사용자가 존재하지 않으면 NotFoundException을 발생시킵니다.
    • 존재하는 경우 해당 사용자를 반환합니다.

    # Nest JS_ jwt.strategt userservice DI를 위한 auth.module.ts코드 수정

    ***auth.module.ts 코드수정***

    ***auth.module.ts 수정코드***

    // auth.module.ts
    
    import { UserModule } from 'src/user/user.module'; // UserModule을 import
    
    import { Module } from '@nestjs/common'; // Nest.js에서 제공하는 Module 데코레이터를 import
    import { ConfigService } from '@nestjs/config'; // ConfigService를 import
    import { JwtModule } from '@nestjs/jwt'; // JwtModule을 import
    import { PassportModule } from '@nestjs/passport'; // PassportModule을 import
    
    import { JwtStrategy } from './jwt.strategy'; // Jwt 전략을 import
    
    @Module({
      imports: [
        PassportModule.register({ defaultStrategy: 'jwt', session: false }), // Passport를 사용하여 JWT 전략을 등록
        JwtModule.registerAsync({
          useFactory: (config: ConfigService) => ({
            secret: config.get<string>('JWT_SECRET_KEY'), // JWT 시크릿 키 설정
          }),
          inject: [ConfigService], // ConfigService 주입
        }),
        UserModule, // UserModule 추가! (사용자 모듈을 현재 모듈에 통합)
      ],
      providers: [JwtStrategy], // Jwt 전략을 제공하는 프로바이더 추가
    })
    export class AuthModule {} // AuthModule 클래스 정의

     

    1. 모듈 및 설정 Import

    • UserModule을 import하여 현재 모듈에서 사용하도록 추가합니다.

    2. Nest.js 모듈 정의

    • @Module 데코레이터를 사용하여 Nest.js 모듈을 정의합니다.

    3. Passport 및 JWT 모듈 등록

    • PassportModule을 사용하여 JWT 전략을 등록합니다. 기본 전략은 'jwt'이며, 세션은 사용하지 않도록 설정합니다.
    • JwtModule을 사용하여 JWT 관련 설정을 비동기적으로 가져오고 등록합니다. 여기서는 ConfigService를 사용하여 JWT 시크릿 키를 가져와 설정합니다.

    4. UserModule 추가

    • UserModule을 현재 모듈에 추가하여 사용자 모듈을 현재 모듈에 통합합니다.

    5. JwtStrategy 프로바이더 추가

    • JwtStrategy를 제공하는 프로바이더를 등록하면 JWT 전략을 사용하는 인증 서비스를 제공할 수 있습니다.

     

Designed by Tistory.