ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Nestjs로 구현하는 Authentication & Authorization(2)
    개발이야기 2021. 3. 26. 23:56

    이 포스트는 NestJS를 사용하여 로그인 API를 구현하는 방법을 정리한 글이다. NestJS 의 공식문서를 참고하였으며 passport, typeorm 을 사용한다. 아래와 같이 4개의 글로 나누어 작성하였으며 이 글에서는 두번째로 NestJS에서 DB와 연동하는 방법을 정리하였다.

     

    전체 코드는 github에 업로드 되어 있다.

    1. DB 연결하기

    1.1 TypeOrm 설치

    NestJS는 TypeOrm을 지원한다. TypeOrm을 사용하기 위해 다음 명령어를 통해 관련 모듈을 설치한다.

    npm install --save @nestjs/typeorm typeorm mysql2

    1.2. DB 연결정보 입력

    NestJS는 기본적으로 root 디렉토리에 있는 ormconfig.json에서 DB와 관련된 정보를 읽어온다. nest-auth 디렉토리에 ormconfig.json 파일을 생성한다.

    # ormconfig.json
    {
      "type": "mysql",
      "host": "localhost",
      "port": 3316,
      "username": "your user name",
      "password": "your password",
      "database": "test",
      "entities": ["dist/**/*.entity{.ts,.js}"],
      "synchronize": true
    }

    app.module.ts에서 이 파일을 읽어 db와 연결할 수 있도록 TypeOrmModule을 추가한다.

    // app.module.ts
    @Module({
      imports: [TypeOrmModule.forRoot()],
      controllers: [AppController],
      providers: [AppService],
    })
    export class AppModule {
      constructor(private connection: Connection){}
    }
    

    3. User 관리

    3.1 User 파일 생성

    사용자에 대한 정보를 생성하고 조회할 수 있도록 user 모듈을 생성한다. 직접 모든 파일을 생성해도 되지만 cli을 통해 간편하게 파일을 생성할 수 있다.

    nest generate module user
    nest generate controller user
    nest generate service user

    NestJS가 자동으로 파일을 생성하고 app.module.ts에 user 모듈을 추가한다.

    3.2. Entity 생성

    db 테이블과 매핑되는 entity를 생성한다. user 디렉토리 밑에 entities 디렉토리를 만들고 user.entity.ts 파일을 생성한다.

    // user.entity.ts
    import { Column, Entity, PrimaryColumn, PrimaryGeneratedColumn } from "typeorm";
    
    @Entity()
    export class User {
      @PrimaryGeneratedColumn()
      seq: number;
    
      @Column()
      userName: string;
    
      @PrimaryColumn()
      userId: string;
    
      @Column()
      password: string;
    
      @Column()
      role: string;
    
    }

    3.3 DTO 생성

    // create-user.dto.ts
    import { IsString } from "class-validator";
    
    export class CreateUserDto {
      @IsString()
      userId: string;
    
      @IsString()
      userName: string;
    
      @IsString()
      password: string;
    
      @IsString()
      role: string;
    }
    npm install --save class-validator class-transformer

    user 회원가입을 위한 dto를 생성한다. class-validator를 사용하여 각 column의 유효성을 검사할 수 있다. class-validator와 class-transformer 모듈을 설치하고 main.ts파일에 pipe를 적용한다.

    // main.ts
    async function bootstrap() {
      const app = await NestFactory.create(AppModule);
      app.useGlobalPipes(new ValidationPipe({
        whitelist: true,
        forbidNonWhitelisted: true,
        transform: true
      }))
      await app.listen(3000);
    }
    bootstrap();

    *3.4 User 모듈 *

    // user.module.ts
    @Module({
      imports: [TypeOrmModule.forFeature([User])],
      providers: [UserService],
      controllers: [UserController],
      exports: [UserService]
    })
    export class UserModule {}

    user 모듈에서 typeorm을 사용할 수 있도록 TypeOrm 모듈을 추가한다. db와 매핑되는 entity를 forFeature에 추가한다.

    3.5 User 컨트롤러

    // user.controller.ts
    @Controller('user')
    export class UserController {
      constructor(private readonly userService: UserService) {}
    
      @Post()
      create(@Body() createUserDto: CreateUserDto): Promise<any> {
        return this.userService.create(createUserDto);
      }
    
      @Get()
      findAll(): Promise<User[]> {
        return this.userService.findAll();
      }
    
      @Get(':id')
      findOne(@Param('id') id: string): Promise<User> {
        return this.userService.findOne(id);
      }
    }

    사용자를 생성하고 생성한 사용자를 조회하는 route를 지정한다. decorator를 사용하여 간편하게 설정할 수 있으며 다음과 같은 요청이 들어왔을 때 처리할 수 있다.

    3.6 User 서비스

    // user.service.ts
    
    @Injectable()
    export class UserService {
      constructor(
        @InjectRepository(User)
        private userRepository: Repository<User>,
      ) {}
    
      async create(createUserDto): Promise<any> {
        const isExist = await this.userRepository.findOne({userId: createUserDto.userId});
        if (isExist) {
          throw new ForbiddenException({
            statusCode: HttpStatus.FORBIDDEN,
            message: [`이미 등록된 사용자입니다.`],
            error: 'Forbidden'
          })
        }
    
        const { password, ...result } = await this.userRepository.save(createUserDto);
        return result;
      }
    
      async findAll(): Promise<User[]> {
        return this.userRepository.find({
          select: ["seq", "userId", "userName", "role"],
        });
      }
    
      findOne(id: string): Promise<User> {
        return this.userRepository.findOne({userId: id}, {
          select: ["seq", "userId", "userName", "role"],
        });
      }
    }
    
    

    db와 연동하여 user를 생성하고 조회하는 로직을 구현한다.

    4. 실행

    npm run start

    위 명령어를 통해 프로젝트를 빌드하고 실행할 수 있다. db에 접속하면 test db에 user 테이블이 자동으로 생성된 것을 확인할 수 있다. 이제 Postman, Insomnia 등의 프로그램을 사용하여 다음과 같은 http 요청을 날려 동작을 확인한다.

    // create
    POST http://localhost:3000/user
    Content-Type: application/json
    
    {
      "userId": "admin",
      "userName": "관리자",
      "password": "admin",
      "role": "admin"
    }
    // findAll
    GET http://localhost:3000/user/
    
    // findOne
    GET http://localhost:3000/user/{id}

    Post 요청을 보내면 body에 있는 정보를 CreateUserDto로 받을 수 있다. Get 요청을 통해 모든 사용자 정보를 조회할 수 있고, userId를 입력하여 특정 사용자의 정보를 조회할 수 있다.

    5. Bcrypt 적용

    bcrypt는 password를 안전하게 저장할 수 있도록 도와주는 hash function이다. password를 db에 저장할 때 평문으로 저장하지 않고 hash값을 저장하며, id/password를 검증할 때 입력한 password의 hash값과 저장된 값이 일치하는지 자동을 계산해주는 편리한 함수를 제공한다. bcrypt를 사용하기 위해 아래 명령어를 입력하여 모듈을 설치한다.

    npm install --save bcrypt
    npm install --save-dev @types/bcrypt

    사용자 정보를 생성할 때 입력한 password를 그냥 저장하지 않고 bcrypt.hash함수를 사용하여 hash값을 저장한다.

    async create(createUserDto: CreateUserDto): Promise<any> {
        const isExist = await this.userRepository.findOne({userId: createUserDto.userId});
        if (isExist) {
          throw new ForbiddenException({
            statusCode: HttpStatus.FORBIDDEN,
            message: [`이미 등록된 사용자입니다.`],
            error: 'Forbidden'
          })
        }
        createUserDto.password = await bcrypt.hash(createUserDto.password, bcryptConstant.saltOrRounds);
        const { password, ...result } = await this.userRepository.save(createUserDto);
        return result;
      }

    첫번째 인자로 password를 주고 두번째 인자로 hash에 사용할 salt 상수값을 입력한다. salt값은 아래와 같이 정의한다.

    // contants.ts
    export const bcryptConstant = {
      saltOrRounds: 10,
    };

    댓글

Designed by Tistory.