반응형

Nestjs에서 기본적으로 제공하는 ValidationPipe를 이용하면 요청 Parameter의 유효성을 쉽게 체크하고 처리할 수 있습니다.

예를 들면 필수 입력 값의 경우 @IsNotEmpty를 사용하는 것만으로 유효성 체크가 끝이 납니다.

 

사용자 등록시 아이디와 패스워드를 필수 값으로 처리하는 방법에 대해 알아보겠습니다.

이전 예제에서 사용자 등록시 Input Parameter는 아래의 UserDTO를 사용하였습니다.

src/auth/dto/user.dto.ts

export class UserDTO {
    username: string;
    password: string;
}

이 경우에 username을 빈값으로 보내게 되어도 다음과 같이 회원 가입이 됩니다.

db에는 다음과 같이 id 3번 값이 공백으로 들어가게 됩니다.

공백이 입력되는 것을 막는 ValidationPipe를 사용해 보겠습니다.

 

우선 class-validator 패키지를 설치합니다.

npm i --save class-validator

UserDTO에서 "@IsNotEmpty()" 데코레이터를 추가합니다.

src/auth/dto/user.dto.ts

import { IsNotEmpty } from "class-validator";

export class UserDTO {
    @IsNotEmpty()
    username: string;
    @IsNotEmpty()
    password: string;
}

 

회원가입 라우팅 메소드에 "@UsePipes(ValidationPipe)"를 추가합니다.

src/auth/auth.controller.ts

import { Body, Controller, Get, Post, Req, Res, UseGuards, UsePipes, ValidationPipe } from "@nestjs/common";
...
@Post('/register')
@UsePipes(ValidationPipe)
async registerAccount(@Req() req: Request, @Body() userDTO: UserDTO): Promise<any> {
    return await this.authService.registerUser(userDTO);
}
...

 

이제 다시 위와 동일하게 username을 공백으로 API를 호출합니다.

* 이때 Validation 체크가 되지 않을 경우 "npm run build"를 한번 실행 후에 하시면 됩니다.

결과는 400 에러가 나오고 , username은 반드시 입력해야 한다는 메시지가 나옵니다.

 

class-validator의 데코레이터는 다음 사이트에가면 확인하실 수 있습니다.

https://github.com/typestack/class-validator#manual-validation

 

GitHub - typestack/class-validator: Decorator-based property validation for classes.

Decorator-based property validation for classes. Contribute to typestack/class-validator development by creating an account on GitHub.

github.com

 

반응형
반응형

이 글의 동영상 강의입니다.

https://youtu.be/hgilx-qB76o

 

AppModule에서 설정했던 TypeORM 설정을 외부 파일로 분리하면 관리가 좀 더 편해집니다.

그리고 Entity 파일도 동적으로 읽어오게 하면 Entity가 생길때마다 추가해야하는 일을 줄일 수 있습니다.

위 2가지 방법으로 기존 소스를 변경해 보겠습니다.

 

기존 AppModule에 TypeOrm은 아래와 같이 설정하였습니다.

src/app.module.ts

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { CatsModule } from './cats/cats.module';
import { Cat } from './cats/entity/cats.entity';
import { AuthModule } from './auth/auth.module';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: 'root',
      database: 'test',
      entities: [Cat],
      synchronize: true,
    }),
    CatsModule,
    AuthModule
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

이를 다음과 같이 외부 설정 파일로 분리할 수 있습니다.

src/orm.config.ts

import { TypeOrmModuleOptions } from '@nestjs/typeorm';

function ormConfig(): TypeOrmModuleOptions {
	const commonConf = {
        SYNCRONIZE: false,
        ENTITIES: [__dirname + '/domain/*.entity{.ts,.js}'],
        MIGRATIONS: [__dirname + '/migrations/**/*{.ts,.js}'],
        CLI: {
            migrationsDir: 'src/migrations',
        },
        MIGRATIONS_RUN: false,
    };

    const ormconfig: TypeOrmModuleOptions = {
        name: 'default',
        type: 'mysql',
        database: 'test',
        host: 'localhost',
        port: 13306,
        username: 'root',
        password: 'root',
        logging: true,
        synchronize: commonConf.SYNCRONIZE,
        entities: commonConf.ENTITIES,
        migrations: commonConf.MIGRATIONS,
        cli: commonConf.CLI,
        migrationsRun: commonConf.MIGRATIONS_RUN,
    };
    
    return ormconfig;
}

export { ormConfig };

Entity 파일들은 동적으로 읽어오기 위해 src/domain 폴더로 옮겨줍니다.

 

이렇게 설정 파일이 만들어지면 AppModule에서는 다음과 같이 변경하면 됩니다.

src/app.module.ts

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { CatsModule } from './cats/cats.module';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AuthModule } from './auth/auth.module';
import { ormConfig } from './orm.config';

@Module({
  imports: [
    TypeOrmModule.forRootAsync({ useFactory: ormConfig }),
    CatsModule,
    AuthModule
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

 

이렇게 해서 좀 더 깔끔한 소스가 되었습니다.^^

반응형
반응형

다음은 이글의 동영상 강의입니다.

https://youtu.be/hJMxGJoNsjQ

https://youtu.be/QBTgOnKMo9s

https://youtu.be/e6HpJqXj1mk

https://youtu.be/d_-Q98-hKs8

 

사이트에서 특정 권한자만 접근이 가능하도록 제어해야할 필요가 있습니다.

예를 들면 회원관리 메뉴의 경우는 모든 사람이 접근하면 안되고, 관리자만 접근 가능해야 하는 경우입니다.

이를 구현하기 위해서는 사용자의 권한(Role)을 관리해야 합니다.

 

권한 관리를 위해서는 다음 사항들이 수행되어야 합니다.

- 권한 구분 : admin, user등으로 권한을 설정합니다.

- 사용자 별로 어떤 권한이 있는지를 db에 저장하고 있어야 합니다.

- 메뉴 접근시 사용자의 권한을 체크하여 사용 가능 여부에 따라 접근을 허가 또는 거부합니다.

 

db에 사용자의 권한을 관리하기 위한 테이블 user_autority의 구조는 다음과 같습니다.

다음을 SQL을 이용하여 user_authority 테이블을 만듭니다.

- user_id는 user 테이블의 id 값에 매핑되는 값입니다.

- authority_name은 다음 두가지 type을 갖도록 ENUM으로 만듭니다.

- authority_name : USER, ADMIN

- 초기 데이터를 아래와 같이 입력합니다.

CREATE TABLE IF NOT EXISTS `test`.`user_authority` (
  `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  `user_id` BIGINT NOT NULL,
  `authority_name` ENUM('ROLE_USER', 'ROLE_ADMIN') NOT NULL,
  PRIMARY KEY (`id`))
ENGINE = InnoDB

insert into `test`.`user_authority` (user_id, authority_name) values (1,'ROLE_USER');
insert into `test`.`user_authority` (user_id, authority_name) values (1,'ROLE_ADMIN');
insert into `test`.`user_authority` (user_id, authority_name) values (2,'ROLE_USER');

- user 테이블에 아래와 같이 id에 값이 있어야 합니다.

 

user-authority entity를 만듭니다.

- 한 사용자가 여러개의 authority를 갖을 수 있으므로 user_authority 테이블에서는 ManyToOne으로 Join을 합니다.

- auth/entity/user-authority.entity.ts

import { Column, Entity, JoinColumn, ManyToOne, PrimaryColumn } from "typeorm";
import { User } from "./user.entity";

@Entity('user_authority')
export class UserAuthority {
    @PrimaryColumn()
    id: number;

    @Column('int',{name: 'user_id'})
    userId: number;

    @Column('varchar',{name: 'authority_name'})
    authorityName: string;

    @ManyToOne(type=>User, user=>user.authorities)
    @JoinColumn({name: 'user_id'})
    user: User;
}

user-entity를 다음과 같이 수정합니다.

- 한 사용자가 여러개의 authority를 갖을 수 있으므로 user 테이블에서는 OneToMany Join을 합니다.

- auth/entity/user.entity.ts

import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm";
import { UserAuthority } from "./user-authority.entity";

@Entity('user')
export class User {
    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    username: string;

    @Column()
    password: string;

    @OneToMany(()=>UserAuthority, userAuthority => userAuthority.user, {eager: true})
    authorities?: any[];
}

authority repository를 만듭니다.

- auth/repository/user-autority.repository.ts

import { EntityRepository, Repository } from "typeorm";
import { UserAuthority } from "../entity/user-authority.entity";

@EntityRepository(UserAuthority)
export class UserAuthorityRepository extends Repository<UserAuthority> {}

App Module의 TypeOrmModule.forRoot에 UserAuthority를 추가합니다.

- app.module.ts

TypeOrmModule.forRoot({
  type: 'mysql',
  host: 'localhost',
  port: 13306,
  username: 'root',
  password: 'root',
  database: 'test',
  entities: [Cat, User, Authority],
  synchronize: true,
}),

Auth Module에 UserAuthorityRepository를 추가합니다.

import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { JwtStrategy } from './security/passport.jwt.strategy';
import { UserRepository } from './repository/user.repository';
import { UserService } from './user.service';
import { UserAuthorityRepository } from './repository/user-authority.repository';

@Module({
  imports: [
    TypeOrmModule.forFeature([UserRepository, UserAuthorityRepository]),
    JwtModule.register({
      secret: 'SECRET_KEY',
      signOptions: {expiresIn: '300s'},
    }),
    PassportModule
  ],
  exports: [TypeOrmModule],
  controllers: [AuthController],
  providers: [AuthService, UserService, JwtStrategy]
})
export class AuthModule {}

payload에도 UserAutyrity를 추가합니다.

- auth/security/payload.interface.ts

export interface Payload {
    id: number;
    username: string;
    authorities?: any[];
}

 

Role Type을 다음과 같이 만듭니다.

- auth/role-type.ts

export enum RoleType {
    USER = 'ROLE_USER',
    ADMIN = 'ROLE_ADMIN',
    ANONYMOUS = 'ROLE_ANONYMOUS',
}

Role Decorator를 다음과 같이 만듭니다.

- auth/decorator/role.decorator.ts

import { SetMetadata } from '@nestjs/common';
import { RoleType } from '../role-type';

export const Roles = (...roles: RoleType[]): any => SetMetadata('roles', roles);

 

Role Guard를 다음과 같이 만듭니다.

- auth/security/roles.guard.ts

import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { User } from '../entity/user.entity';

@Injectable()
export class RolesGuard implements CanActivate {
    constructor(private readonly reflector: Reflector) {}

    canActivate(context: ExecutionContext): boolean {
        const roles = this.reflector.get<string[]>('roles', context.getHandler());

        if (!roles) {
            return true;
        }

        const request = context.switchToHttp().getRequest();
        const user = request.user as User;

        return user && user.authorities && user.authorities.some(role => roles.includes(role));
    }
}

RolesGuard에서 사용된 Reflector는 Application Runtime시에 정의된 Metadata를 이용해 type을 알아내는데 사용합니다.

 

AuthService의 payload에 authority를 추가합니다.

- authorities는 배열 형태로 RoleType이 들어갑니다.

- auth/auth.service.ts

async validateUser(userDTO: UserDTO): Promise<{accessToken: string} | undefined> {
    let userFind: User = await this.userService.findByFields({
        where: { username: userDTO.username }
    });
    if(!userFind) {
        throw new UnauthorizedException();
    }
    const validatePassword = await bcrypt.compare(userDTO.password, userFind.password);
    if(!validatePassword) {
        throw new UnauthorizedException();
    }

    this.convertInAuthorities(userFind);

    const payload: Payload = { id: userFind.id, username: userFind.username, authorities: userFind.authorities };
    return {
        accessToken: this.jwtService.sign(payload)
    };
}

async tokenValidateUser(payload: Payload): Promise<User| undefined> {
    const userFind = await this.userService.findByFields({
        where: { id: payload.id }
    });
    this.flatAuthorities(userFind);
    return userFind;
}

private flatAuthorities(user: any): User {
    if (user && user.authorities) {
        const authorities: string[] = [];
        user.authorities.forEach(authority => authorities.push(authority.authorityName));
        user.authorities = authorities;
    }
    return user;
}

private convertInAuthorities(user: any): User {
    if (user && user.authorities) {
        const authorities: any[] = [];
        user.authorities.forEach(authority => authorities.push({ name: authority.authorityName }));
        user.authorities = authorities;
    }
    return user;
}

테스트를 위해 Controller에 핸들러를 하나 추가합니다.

- auth/auth.controller.ts

@Get('/admin-role')
@UseGuards(AuthGuard, RolesGuard)
@Roles(RoleType.ADMIN)
adminRole(@Req() req: Request): any {
    const user: any = req.user;
    return user;
}

Application을 실행합니다.

npm run start:dev

 

ADMIN 권한이 있는 아이디로 테스트 

- 관리자 계정으로 로그인 후 토큰값을 받아 옵니다.

accessToken을 이용해서 관리자 접근 권한을 체크합니다.

ADMIN 권한이 없는 아이디로 테스트

권한이 없을 경우 아래와 가티 에러가 발생됩니다.

이상으로 Nest에서 권한을 체크하는 방법에 대해서 알아보았습니다.

반응형
반응형

다음은 이 글의 동영상 강의 입니다.

https://youtu.be/1upOqWW2zyU

 

Nestjs에서 JWT 토큰을 이용한 인증시에는 Guard를 사용합니다.

Guard는 라우팅 전에 작동하는 일종의 미들웨어입니다.

 

이전까지의 코드가 들어있는 샘플소스는 다음 github 저장소에서 받으실 수 있습니다.

https://github.com/CodeGearGit/nestjs-05-jwt

 

GitHub - CodeGearGit/nestjs-05-jwt

Contribute to CodeGearGit/nestjs-05-jwt development by creating an account on GitHub.

github.com

 

패키지 설치

프로젝트에 다음과 같이 패키지를 설치합니다.

npm i --save @nestjs/passport @types/passport-jwt

 

코드 작성

JWT 검증을 위해 JwtStrategy를 만듭니다.

auth/security/passport.jwt.strategy.ts

import { Injectable, UnauthorizedException } from "@nestjs/common";
import { ExtractJwt, Strategy, VerifiedCallback } from 'passport-jwt';
import { PassportStrategy } from "@nestjs/passport";
import { AuthService } from "../auth.service";
import { Payload } from "./payload.interface";

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy){
    constructor(private authService: AuthService){
        super({
            jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
            ignoreExpiration: true,
            secretOrKey: 'SECRET_KEY',
        })
    }

    async validate(payload: Payload, done: VerifiedCallback): Promise<any> {
        const user = await this.authService.tokenValidateUser(payload);
        if (!user) {
            return done(new UnauthorizedException({ message: 'user does not exist' }), false);
        }

        return done(null, user);
    }
}

auth/auth.service.ts 에 tokenValidateUser를 추가합니다.

async tokenValidateUser(payload: Payload): Promise<UserDTO | undefined> {
    return await this.userService.findByFields({
        where: { id: payload.id }
    });
}

auth/auth.module.ts

- imports에 PassportModule 을 추가하고,

- providers에 JwtStrategy 를 추가합니다.

import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { JwtStrategy } from './security/passport.jwt.strategy';
import { UserRepository } from './user.repository';
import { UserService } from './user.service';

@Module({
  imports: [
    TypeOrmModule.forFeature([UserRepository]),
    JwtModule.register({
      secret: 'SECRET_KEY',
      signOptions: {expiresIn: '300s'},
    }),
    PassportModule
  ],
  exports: [TypeOrmModule],
  controllers: [AuthController],
  providers: [AuthService, UserService, JwtStrategy]
})
export class AuthModule {}

auth/security/auth.guard.ts를 만듭니다.

import { ExecutionContext, Injectable } from '@nestjs/common';
import { AuthGuard as NestAuthGuard } from '@nestjs/passport';

@Injectable()
export class AuthGuard extends NestAuthGuard('jwt') {
    canActivate(context: ExecutionContext): any {
        return super.canActivate(context);
    }
}

auth/auth.controller.ts에 아래와 같이 라우터를 하나 추가합니다.

@Get('/authenticate')
@UseGuards(AuthGuard)
isAuthenticated(@Req() req: Request): any { 
    const user: any = req.user;
    return user;
}

 

테스트

jwt 인증 모듈의 개발이 완료되었으므로 Postman을 이용해서 테스트를 해봅니다.

토큰이 없을 경우 다음과 같이 Unauthorized 값이 리턴됩니다.

로그인 호출을 통해 토큰 값을 받아옵니다.

토큰을 이용해 authenticate를 다시 호출합니다.

- Headers 에 다음 값을 입력합니다.

   KEY: Authorization

   VALUE : 로그인에서 받아온 토큰값

다음과 같이 결과가 나오는걸 보실 수 있습니다.

이상으로 JWT 인증하는 법에 대해 알아보았습니다.

 

최종 완료된 소스는 아래 URL에서 확인하시면 됩니다.

https://github.com/CodeGearGit/nestjs-06-jwt-auth

 

GitHub - CodeGearGit/nestjs-06-jwt-auth

Contribute to CodeGearGit/nestjs-06-jwt-auth development by creating an account on GitHub.

github.com

 

반응형
반응형

다음은 이글의 동영상 강의 입니다.

https://youtu.be/H4VS-Osylvo

 

토큰을 이용한 API 호출 방법

REST API 호출시 토큰을 이용하면 사용자 인증과 권한을 체크할 수 있습니다.

다음 그림은 토큰의 생성과  호출을 나타내는 그림입니다.

  1. Client에서 로그인을 요청합니다.
  2. Server에서 로그인을 체크한 후 토큰을 생성하고 accessToken을 전달합니다.
  3. API를 호출합니다. 이때 호출 Header에 accessToken을 담아서 호출합니다.
  4. accessToken을 확인하여 인증여부, 권한등을 체크한 후 결과 값을 return합니다.

 

JWT 구조

JWT 토큰은 다음과 같이 "."을 구분자로 하여 다음과 같이 3개의 값이 들어있는 형태를 갖고 있습니다.

  1. header
  2. payload
  3. verify signature
{
    "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
     eyJpZCI6MSwidXNlcm5hbWUiOiJjb2RlZ2VhciIsImlhdC
     I6MTY0MzUxOTI2MCwiZXhwIjoxNjQzNTE5NTYwfQ.
     jxsk2FtHsRRhoAZrsUDgHaHOLCxI9IlSMKTrkZ0zUl4"
}

이 값은 www.jwt.io  사이트에 가면 다음과 같이 확인이 가능합니다.

 

JWT.IO

JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.

jwt.io

 

샘플 소스

지난시간까지의 소스는 아래 URL에서 받으시고, 아래 코드를 작성하시면 됩니다.

https://github.com/CodeGearGit/nestjs-04-login

 

GitHub - CodeGearGit/nestjs-04-login

Contribute to CodeGearGit/nestjs-04-login development by creating an account on GitHub.

github.com

 

JwtModule Import

우선 로그인시에 토큰을 생성해 보는 방법에 대해 설명합니다.

 

Nestjs에서 jwt 토큰을 사용하기 위해서는 @nestjs/jwt 라는 패키지를 설치합니다.

npm i --save @nestjs/jwt

JWT를 사용하고자 하는 Module에 다음과 같이 Jwt.Module을 등록합니다.

auth.module.ts

import { JwtModule } from '@nestjs/jwt';
imports: [
    TypeOrmModule.forFeature([UserRepository]),
    JwtModule.register({
      secret: 'SECRET',
      signOptions: { expiresIn: '300s' },
    }),
 ],

여기서 secret에는 verify signature에서 사용할 secret key 값을 입력합니다.

 

Payload Interface

다음은 payload에 보여 줄 값을 위해 interface를 생성합니다.

auth/security/payload.interface.ts

export interface Payload {
    id: number;
    username: string;
}

 

 

JWT토큰 생성

로그인 서비스를 다음과 같이 수정합니다.

auth/auth.service.ts의 validateUser

  • payload 값 지정
  • Json의 token 값으로 return
async validateUser(userDTO: UserDTO): Promise<{accessToken: string} | undefined> {
    let userFind: User = await this.userService.findByFields({
        where: { username: userDTO.username }
    });
    const validatePassword = await bcrypt.compare(userDTO.password, userFind.password);
    if(!userFind || !validatePassword) {
        throw new UnauthorizedException();
    }

    const payload: Payload = { id: userFind.id, username: userFind.username };

    return {
        accessToken: this.jwtService.sign(payload),
    };
}

 

로그인 컨트롤러를 다음과 같이 수정합니다.

@Post('/login')
async login(@Body() userDTO: UserDTO, @Res() res: Response): Promise<any> {
    const jwt = await this.authService.validateUser(userDTO);
    res.setHeader('Authorization', 'Bearer ' + jwt.accessToken);
    return res.json(jwt);
}

서버를 실행하고 Postman을 이용해 다음과 같이 로그인을 확인합니다.

POST http://localhost:3000/auth/login

결과

결과 값에서 Headers 탭을 클릭하면 다음과 같이 Bearer + 토큰 값을 확인할 수 있습니다.

 

이상으로 Nestjs에서 JWT 토큰을 생성하는 법을 알아보았습니다.

반응형
반응형

다음은 이 글의 동영상 강의 입니다.

https://youtu.be/LbyfF6ALMo0

 

비밀번호 암호와의 필요성

회원 가입시 비밀번호를 그대로 저장하면 보안사고의 위험이 있습니다.
만약 고객의 정보가 노출되었을 경우 해당 비밀번호를 이용하여 다른 사이트를 해킹할 경우 많은 피해가 발생할 수 있습니다.
따라서 실제 프로젝트에서 비밀번호는 반드시 암호화하여 저장해야만 합니다.
이번 글에서는 비밀번호를 암호화하는 방법에 대해 알아보겠습니다.
 

BCrypt

여기서는 nodejs에서 비밀번호 암호화시 많이 사용하는 bcrypt라는 패키지를 사용합니다.
다음은 npmjs.com의 bcrypt 설명페이지입니다.
 
bcrypt는 비밀번호를 암호화하는 키방식의 암호화 함수 입니다.

 

예제 소스 다운로드

여기서 사용하는 예제 소스는 아래 URL에서 받으실 수 있습니다.

https://github.com/CodeGearGit/nest-sample

 

GitHub - CodeGearGit/nest-sample: nestjs sample program

nestjs sample program. Contribute to CodeGearGit/nest-sample development by creating an account on GitHub.

github.com

 

bcrypt  추가

기존 프로젝트에 bcrypt 패키지를 설치합니다.
nestjs가 typescript 기반이므로 @types/bcrypt를 사용합니다.
npm install --save bcrypt @types/bcrypt
 

회원 가입 시 비밀번호 암호화

비밀번호 암호화 소스를 아래와 같이 작성합니다.
auth/user.service.ts
import * as bcrypt from 'bcrypt';

async transformPassword(user: UserDTO): Promise<void> {
	user.password = await bcrypt.hash(
		user.password, 10,
	);
	return Promise.resolve();
}

 

회원 정보를 저장하기 전에 패스워드 암호화 로직을 추가합니다.
auth/user.service.ts
async save(userDTO: UserDTO): Promise<UserDTO | undefined> {
	await this.transformPassword(userDTO);
	console.log(userDTO);
	return await this.userRepository.save(userDTO);
}

 

포스트맨을 이용해 사용자를 생성하는 API를 호출합니다.

결과

Database에는 다음과 같이 들어가 있는 것을 볼 수 있습니다.

 

 

로그인시 패스워드 체크 로직 수정

로그인시 패스워드 확인하는 부분을 수정합니다.
이때 bcrypt의 compare를 사용하여 입력받은 password와 저장된 password를 비교합니다.
auth.service.ts
import * as bcrypt from 'bcrypt';

async validateUser(userDTO: UserDTO): Promise<string | undefined> {
	let userFind: UserDTO = await this.userService.findByFields({
		where: { username: userDTO.username }
	});
	const validatePassword = await bcrypt.compare(userDTO.password, userFind.password)
	if(!userFind || !validatePassword) {
		throw new UnauthorizedException();
	}
	return "Login Success!";
}
포스트맨을 이용해 로그인을 테스트합니다.
 

결과

로그인 정보를 잘못 입력한 경우의 결과
이상으로 비밀번호 암호화 하는 방법에 대해 알아보았습니다.
반응형
반응형

다음은 이 글의 동영상 강의 입니다.

https://youtu.be/u39nqCfjYx4

 

가장 기본적인 아이디 / 패스워드를 체크하는 수준의 로그인을 만들어 봅니다.

이전 글 회원가입의 소스는 아래 url을 통해 받으실 수 있습니다.

https://github.com/CodeGearGit/nest-register-sample

 

GitHub - CodeGearGit/nest-register-sample

Contribute to CodeGearGit/nest-register-sample development by creating an account on GitHub.

github.com

 

auth/auth.service.ts에 아래와 같이 로그인 체크 로직을 추가합니다.

async validateUser(user: UserDTO): Promise<UserDTO | undefined> {
    let userFind: UserDTO = await this.userService.findByFields({ 
        where: { username: user.username }
    });
    if(!userFind || user.password !== userFind.password) {
        throw new UnauthorizedException();
    }
    return userFind;
}

auth/auth.controller.ts에 라우팅 메소드를 추가합니다.

@Post('/login')
async login(@Body() userDTO: UserDTO): Promise<any> {
    return await this.authService.validateUser(userDTO);
}

Postman을 이용해서 아래와 같이 login 테스트를 합니다.

결과 값은 다음과 같습니다.

password를 '1234' 입력하면 결과는 다음과 같습니다.

 

이상으로 가장 기초적인 로그인 체크에 대해 알아보았습니다.

반응형
반응형

다음은 이 글의 동영상 강의 입니다.

https://youtu.be/4mjd5P7cZIA

https://youtu.be/NCqCfyQoNz8

 

Nest로 로그인 기능을 구현하기 위해 우선적으로 회원 가입을 만들어봅니다.

 

이 예제의 소스는 아래 사이트에서 받으실 수 있습니다.

https://github.com/CodeGearGit/nest-register-sample

 

GitHub - CodeGearGit/nest-register-sample

Contribute to CodeGearGit/nest-register-sample development by creating an account on GitHub.

github.com

 

USER 테이블의 구조는 다음과 같습니다.

auth 모듈을 만듭니다.

nest g module auth

auth/entity/user.entity.ts 를 만듭니다.

import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";

@Entity('user')
export class User{
    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    username: string;
    
    @Column()
    password: string;
}

auth/user.repository.ts 를 만듭니다.

import { EntityRepository, Repository } from "typeorm";
import { User } from "./entity/user.entity";

@EntityRepository(User)
export class UserRepository extends Repository<User>{}

auth/dto/user.dto.ts 를 만듭니다.

export class UserDTO {
    username: string;
    password: string;
}

auth/user.servict.ts 를만듭니다.

import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { FindOneOptions } from "typeorm";
import { UserDTO } from "./dto/user.dto";
import { UserRepository } from "./user.repository";

@Injectable()
export class UserService{
    constructor(@InjectRepository(UserRepository) private userRepository: UserRepository){}

    async findByFields(options: FindOneOptions<UserDTO>): Promise<UserDTO | undefined> {
        return await this.userRepository.findOne(options);
    }

    async save(userDTO: UserDTO): Promise<UserDTO | undefined> {
        return await this.userRepository.save(userDTO);
    }
}

auth/auth.service.ts 를 만듭니다.

(nest g service auth)

import { HttpException, HttpStatus, Injectable } from "@nestjs/common";
import { UserDTO } from "./dto/user.dto";
import { UserService } from "./user.service";

@Injectable()
export class AuthService {
    constructor(
        private userService: UserService
    ){}

    async registerNewUser(newUser: UserDTO): Promise<UserDTO> {
        let userFind: UserDTO = await this.userService.findByFields({ where: { username: newUser.username } });
        if(userFind){
            throw new HttpException('Username already used!', HttpStatus.BAD_REQUEST);
        }
        return this.userService.save(newUser);
    }
}

auth/auth.controller.ts를 만듭니다.

(nest g controller auth)

import { Body, Controller, Post, Req } from "@nestjs/common";
import { Response, Request } from 'express';
import { AuthService } from "./auth.service";
import { UserDTO } from "./dto/user.dto";

@Controller('api')
export class AuthController {
    constructor(private authService: AuthService){}

    @Post('/register')
    async registerAccount(@Req() req: Request, @Body() userDTO: UserDTO): Promise<any> {
        return await this.authService.registerNewUser(userDTO);
    }
}

auth/auth.module.ts를 아래와 같이 수정합니다.

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { UserRepository } from './user.repository';
import { UserService } from './user.service';

@Module({
    imports: [TypeOrmModule.forFeature([UserRepository])],
    exports: [TypeOrmModule],
    controllers: [AuthController],
    providers: [AuthService, UserService]
  })
export class AuthModule {}

서버를 start 합니다.

npm run start:dev

postman에서 다음과 같이 테스트를 합니다.

POST http://localhost:3000/api/register
Body-raw-JSON
{
    "username": "codegear",
    "password": "1111"
}

실행 결과는 다음과 같습니다.

user 테이블에서도 다음과 같이 확인하실 수 있습니다.

 

반응형
반응형

이 글은 업데이트 되었습니다. 아래 링크를 참조하세요.

https://codegear.tistory.com/116

 

NestJS - 20. TypeORM (2023)

목차 TypeORM이란? NestJS에서 TypeORM 사용법 TypeORM이란? ORM은 Object-Relational Mapping의 약자입니다. 여기서 Object는 객체지향언어에서 말하는 객체를 의미합니다. Relational은 관계형 데이터베이스(Relational

codegear.tistory.com

----------------------------------------------------------

이 내용의 동영상 강의 2편입니다.

https://youtu.be/OTz22O8-PkE

https://youtu.be/t5XyFhe3sPA

 

Nest에서 db를 연결하여 개발을 진행해야할 경우, 좋은 선택지 중 하나가 TypeORM을 사용하는 것입니다.

 

TypeORM연결을 위해서는 DB가 설치 되어 있어야 합니다.

우리는 Docker를 이용해서 MySQL을 빠르게 설치하고 사용해보겠습니다.

MySQL 설치는 이전 글을 참고해 주세요.

https://codegear.tistory.com/66

 

MySQL 쉽게 설치하기 - with Docker

이 글의 동영상 강의입니다. https://youtu.be/5e0axgEP8EE 우선 아래 사이트에 가서 Docker를 설치합니다. https://www.docker.com/get-started Get Started with Docker | Docker Learn about the complete cont..

codegear.tistory.com

MySQL Workbench를 이용해서 test schema를 생성합니다.

CREATE SCHEMA test DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci;

 

이전까지 진행한 Nestjs 소스는 github에서 받으실 수 있습니다.

https://github.com/CodeGearGit/nest-cats

 

GitHub - CodeGearGit/nest-cats

Contribute to CodeGearGit/nest-cats development by creating an account on GitHub.

github.com

 

먼저 TypeORM을 설치합니다.

npm install --save @nestjs/typeorm typeorm mysql2

설치가 완료되면 app.module.ts에 TypeORM 모듈을 import 합니다.

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { CatsModule } from './cats/cats.module';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: 'root',
      database: 'test',
      entities: [],
      synchronize: true,
    }),
    CatsModule
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

* 주의 synchronize: true는 운영에서는 사용하지 마세요. 

 

다음과 같이 cats/entity/cats.entity.ts 파일을 만듭니다.

import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class Cat {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @Column()
  age: number;

  @Column()
  breed: string;

  @Column({ default: true })
  isActive: boolean;
}

app.module.ts에 cats.entity를 추가합니다.

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { CatsModule } from './cats/cats.module';
import { Cat } from './cats/entity/cats.entity';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: 'root',
      database: 'test',
      entities: [Cat],
      synchronize: true,
    }),
    CatsModule
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

cats.module.ts에서 cats.entity를 사용하기 위해 다음과 같이 소스를 추가합니다.

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
import { Cat } from './entity/cats.entity';

@Module({
  imports: [TypeOrmModule.forFeature([Cat])],
  exports: [TypeOrmModule],
  controllers: [CatsController],
  providers: [CatsService]
})
export class CatsModule {}

cats.service.ts에 catsRepository를 생성하여 data를 핸들링할 수 있게 합니다.

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Cat } from './entity/cats.entity';
import { getConnection } from "typeorm";

@Injectable()
export class CatsService {
    constructor(
        @InjectRepository(Cat)
        private catsRepository: Repository<Cat>,
      ) {}
    
      findAll(): Promise<Cat[]> {
        return this.catsRepository.find();
      }
    
      findOne(id: string): Promise<Cat> {
        return this.catsRepository.findOne(id);
      }

      async create(cat: Cat): Promise<void> {
        await this.catsRepository.save(cat);
      }
    
      async remove(id: number): Promise<void> {
        await this.catsRepository.delete(id);
      }

      async update(id: number, cat: Cat): Promise<void> {
        const existCat = await this.catsRepository.findOne(id);
        if(existCat){
            await getConnection()
                .createQueryBuilder()
                .update(Cat)
                .set({ 
                    name: cat.name, 
                    age: cat.age,
                    breed: cat.breed
                })
                .where("id = :id", { id })
                .execute();
        }
      }
}

cats.controller.ts를 다음과 같이 작성합니다.

import { Body, Controller, Delete, Get, Param, Post, Put } from '@nestjs/common';
import { CatsService } from './cats.service';
import { Cat } from './entity/cats.entity';

@Controller('cats')
export class CatsController {
    constructor(private catsService: CatsService){};

    @Get()
    findAll(): Promise<Cat[]> {
        return this.catsService.findAll();
    }

    @Get(':id')
    findOne(@Param('id')id: string): string {
        return `This action returns a #${id} cat`;
    }

    @Post()
    create(@Body() cat: Cat){
        return this.catsService.create(cat);
    }

    @Put(':id')
    update(@Param('id')id: number, @Body() cat: Cat){
        this.catsService.update(id, cat);
        return `This action updates a #${id} cat`;
    }

    @Delete(':id')
    remove(@Param('id')id: number){
        this.catsService.remove(id);
        return `This action removes a #${id} cat`;
    }
}

이제 Postman을 이용해서 테스트를 합니다.

POST를 이용해 데이터를 생성합니다.

GET으로 조회를 하면 다음과 같습니다.

다시 POST 로 추가 데이터를 입력합니다.

다시 GET으로 전체 데이터를 조회합니다.

수정, 삭제등도 테스트 하시면 됩니다.

 

반응형
반응형

이 글의 동영상 강의입니다.

https://youtu.be/5e0axgEP8EE

 

우선 아래 사이트에 가서 Docker를 설치합니다.

https://www.docker.com/get-started

 

Get Started with Docker | Docker

Learn about the complete container solution provided by Docker. Find information for developers, IT operations, and business executives.

www.docker.com

 

다음은 프로젝트 루트 폴더에 다음과 같이 docker-compose.yml을 생성합니다.

version: '3'
services:
  local-db:
    image: library/mysql:5.7
    container_name: local-db
    restart: always
    ports:
      - 13306:3306
    environment:
      MYSQL_ROOT_PASSWORD: root
      TZ: Asia/Seoul
    volumes:
      - ./db/mysql/data:/var/lib/mysql
      - ./db/mysql/init:/docker-entrypoint-initdb.d
    platform: linux/x86_64

 아래 스크립트를 실행합니다.

docker-compose up -d

설치가 완료되면 Docker Dashboard에 아래와 같이 mysql 이 추가됩니다.

우측의 start 버튼을 클릭하여 서버를 실행합니다.

MySQL Workbench를 이용해서 db에 접속합니다.

접속 정보는 다음과 같습니다.

 

이후로는 MySQL Schema와 사용자를 생성하시면 됩니다.

 

반응형

+ Recent posts