nest js

NestJs JWT 토큰 생성 / 토큰 인증(Guard)

호리둥절 2023. 4. 14. 16:26

이전에 로그인 / 로그아웃을 구현하지 않으셨다면 아래의 포스팅을 봐주세요!

 

https://develop-const.tistory.com/22

 

NestJs 회원가입(비밀번호 암호화), 로그인 구현하기

인증관련부분은 다음시간에 구현하고, 먼저 회원가입과 로그인기능에 대해 구현해보겠습니다. auth와 user 파일을 간단하게 생성 nest g resource auth nest g resource user user [ user/entities/user.entitiy.ts ] impor

develop-const.tistory.com

 

토큰을 사용해 api 호출하는 흐름

1. 클라이언트는 서버에 로그인 요청을 합니다.

2. email과 password가 맞는지 검증한후 맞다면 access 토큰을 발급해 전달합니다

3. 발급받은 access토큰을 header에 담아 api 요청을 합니다.

4. request에서 회원정보를 받아볼 수 있습니다.

 

jwt 토큰 생성하기

 @nestjs/jwt 라는 패키지를 설치

npm i --save @nestjs/jwt

[ auth/auth.module.ts ] 

import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { UserModule } from 'src/user/user.module';

@Module({
  imports: [
    UserModule,
    JwtModule.register({
      secret: 'secret',
      signOptions: { expiresIn: '300s' },
    }),
  ],
  controllers: [AuthController],
  providers: [AuthService],
})
export class AuthModule { }

secret key는 반드시 바꿔주셔야 합니다. 강의 따라하다가 cecret이라고 작성하시다간 보안 뚫려요!

 

[ auth/auth.controller.ts ]

@Controller()
export class AuthController {
  constructor(private readonly userService: UserService, private readonly jwtService: JwtService) { }

  @Post('/signin')
  async signin(@Body() authDTO: AuthDTO.SignIn) {
    const { email, password } = authDTO;

    const user = await this.userService.findByEmail(email);
    if (!user) {
      throw new UnauthorizedException('이메일 또는 비밀번호를 확인해 주세요.');
    }

    const isSamePassword = bcrypt.compareSync(password, user.password);
    if (!isSamePassword) {
      throw new UnauthorizedException('이메일 또는 비밀번호를 확인해 주세요.');
    }

    const payload = {
      id: user.id,
    }
    
    const accessToken = this.jwtService.sign(payload)

    return accessToken;
  }
}

1. JwtService를 사용하기위해 JwtService를 생성자에서 주입받습니다.

2. user 로그인 후 id를 얻어와 payload에 넣어줍니다

3. jwtService.sign에 payload를 담아 요청한 후 jwt accessToken을 반환 받아 accessToken에 저장한후 return 합니다.

 

 

jwt 토큰 인증하기

아래의 포스팅을 참고하여 직접 jwt 토큰을 구현해도 되지만 오늘은 passport-jwt를 사용해보겠습니다.

https://develop-const.tistory.com/12

 

NestJs Guard에 대해 알아보자

Guard란? guard는 요청의 처리 여부를 결정하는 미들웨어 역할을 합니다. guard는 인증과 권한 부여 등 요청에 대한 검사를 처리하는 데 주로 사용합니다. guard는 @nestjs/common의 CanActivate 인터페이스를

develop-const.tistory.com

passport-jwt 설치하기

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

[ auth/security/passport.jwt.ts ]

import { Injectable, UnauthorizedException } from "@nestjs/common";
import { PassportStrategy } from "@nestjs/passport";

import { ExtractJwt, Strategy, VerifiedCallback } from 'passport-jwt';
import { UserEntity } from "src/user/entities/user.entity";
import { UserService } from "src/user/user.service";

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(private userService: UserService) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: 'secret',
    })
  }

  async validate(payload: Payload, done: VerifiedCallback): Promise<UserEntity> {
    const { id } = payload
    const user = await this.userService.findById(id)
    if (!user) {
      throw new UnauthorizedException({ message: '회원 존재하지 않음.' });
    }

    return user;
  }
}

export interface Payload {
  id: number;
  //role:string
}

JwtStrategy는 Passport 라이브러리의 PassportStrategy 클래스를 상속하고 있습니다. 이를 통해 Passport를 사용한 인증 기능을 구현할 수 있습니다. JwtStrategy는 JSON Web Token(JWT)을 사용하여 인증을 처리합니다.

 

JwtStrategy 클래스의 생성자에서는 jwtFromRequest와 secretOrKey를 설정하고 있습니다. jwtFromRequest는 JWT를 추출하는 방법을 설정하는 옵션입니다. secretOrKey는 앞서 설정해주었던 secret와 동일하게 설정해주어야 합니다.

 

JwtStrategy는 Passport 라이브러리의 PassportStrategy 클래스를 상속하고 있습니다. 이를 통해 Passport를 사용한 인증 기능을 구현할 수 있습니다. JwtStrategy는 JSON Web Token(JWT)을 사용하여 인증을 처리합니다.

 

JwtStrategy에서 사용하는 validate 함수는 JWT에서 추출한 payload를 검증하고, 검증된 결과로 UserEntity 객체를 반환합니다. 검증에 실패하면 UnauthorizedException 예외를 발생시켜 인증에 실패했음을 알려줍니다.

 

Payload는 JWT에 담기는 정보를 정의한 인터페이스입니다. 이 예제에서는 id를 담고 있으며, 필요한 경우 role 등의 정보도 추가할 수 있습니다.

 

[ auth/guard/auth.guard.ts ]

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

[ auth/auth.module.ts ]

import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';

import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { UserModule } from 'src/user/user.module';
import { JwtStrategy } from './security/passport.jwt';

@Module({
  imports: [
    UserModule,
    JwtModule.register({
      secret: 'secret',
      signOptions: { expiresIn: '300s' },
    }),
    PassportModule
  ],
  controllers: [AuthController],
  providers: [AuthService, JwtStrategy]
})
export class AuthModule { }
  • PassportModule: NestJS에서 제공하는 Passport 라이브러리를 사용하기 위한 모듈입니다.
  • JwtStrategy를 providers에 등록하여 JWT 검증 로직을 담당하도록 합니다.
  • UserModule은 userService기능을 jwJwtStrategy 클래스안에서 사용하고 있기 때문에 import 해줍니다.

 

[ user/user.controller.ts ]

자 이제 만들어둔 guard를 프로필을 얻어오는데 사용해보겠습니다.

import {
  Body,
  ConflictException,
  Controller, Get, Post, Req, UseGuards, Request
} from '@nestjs/common';

import { UserService } from './user.service';
import { AuthDTO } from 'src/auth/dto/authDto';
import { UserEntity } from './entities/user.entity';
import { AuthGuard } from 'src/auth/guard/auth.guard';

@Controller('user')
export class UserController {
  constructor(private readonly userService: UserService) { }

  @Post('/signup')
  async signup(@Body() authDTO: AuthDTO.SignUp) {

    const { email, nickname } = authDTO;

    const hasEmail = await this.userService.findByEmail(email);
    if (hasEmail) {
      throw new ConflictException('이미 사용중인 이메일 입니다.');
    }

    const hasNickname = await this.userService.findByNickname(nickname);
    if (hasNickname) {
      throw new ConflictException('이미 사용중인 닉네임 입니다.');
    }

    const userEntity = await this.userService.create(authDTO);

    return '회원가입성공';
  }

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

UseGuards를 통해 AuthGuard를 등록해주고 req 결과로 user 정보를 받습니다.

 

결과화면