반응형

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

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으로 전체 데이터를 조회합니다.

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

 

반응형

+ Recent posts