백앤드(Back-End)/Nest.js

[Nest.js] Nest.js API 만들기 (9) - Authentication(Passport-Jwt) & Guards 로그인 인증

RyanSin 2021. 11. 20. 02:06
반응형

- 지난 시간

안녕하세요. 지난 시간에는 Passport-Local 방식을 사용해서 일반 로그인 기능을 구현해봤습니다.

 

혹시 놓치고 오신 분들은 아래 링크를 통해 학습하고 오시는 걸 추천드리겠습니다.

[Nest.js] Nest.js API 만들기 (8) - Authentication(Passport, Passport-Local) & Guards 로그인

 

[Nest.js] Nest.js API 만들기 (8) - Authentication(Passport, Passport-Local) & Guards 로그인

import { Strategy } from 'passport-local'; import { PassportStrategy } from '@nestjs/passport'; import { Injectable, UnauthorizedException } from '@nestjs/common'; import { AuthService } from '../au..

any-ting.tistory.com

 

- 개요

이번 시간에는 지난 시간에 이어서 Jwt 토큰을 활용해 인증 절차를 구현해보는 시간을 가져보겠습니다.

 

Jwt 기능을 사용하기 위해서는 몇 가지 모듈을 설치해야 합니다.

#npm
npm install --save @nestjs/jwt passport-jwt
npm install --save-dev @types/passport-jwt

#yarn
yarn add @nestjs/jwt passport-jwt
yarn add @types/passport-jwt --dev

 

 

모듈을 설치했다면 guards/jwt-auth.guard.ts 파일과 strategies/auth.jwt.strategy.ts 파일을 생성합니다.

 

- jwt-auth.guard.ts

import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}

 

- auth.jwt.strategy.ts

import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { jwtConstants } from '../constants';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      //Request에서 JWT 토큰을 추출하는 방법을 설정 -> Authorization에서 Bearer Token에 JWT 토큰을 담아 전송해야한다.
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      //true로 설정하면 Passport에 토큰 검증을 위임하지 않고 직접 검증, false는 Passport에 검증 위임
      ignoreExpiration: false,
      //검증 비밀 값(유출 주위)
      secretOrKey: jwtConstants.secret,
    });
  }

  /**
   * @author Ryan
   * @description 클라이언트가 전송한 Jwt 토큰 정보
   *
   * @param payload 토큰 전송 내용
   */
  async validate(payload: any) {
    return { userId: payload.sub, username: payload.username };
  }
}

 

jwt 토큰 전략을 생성하고 해당 jwt 토큰 전략을 연결합니다.

 

토큰 검증을 완료하면 validate() 메서드가 실행됩니다. 토큰이 만료되거나 문제가 발생하면 Passport는 Error를 발생합니다.

이런 예외처리는 직접 구현해야 합니다.(이 부분은 다음에 추가하도록 하겠습니다.)

 

이제 auth.module.ts 파일에 JwtModeule과 JwtStrategy를 등록합니다.

import { Module } from '@nestjs/common';
import { PassportModule } from '@nestjs/passport';
import { LocalStrategy } from './strategies/auth.local.strategy';
import { AuthService } from './auth.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UserRepository } from 'src/repository/user.repository';
import { JwtModule } from '@nestjs/jwt';
import { jwtConstants } from './constants';
import { JwtStrategy } from './strategies/auth.jwt.strategy';

@Module({
  imports: [
    PassportModule,
    JwtModule.register({
      //토큰 서명 값 설정
      secret: jwtConstants.secret,
      //토큰 유효시간 (임의 60초)
      signOptions: { expiresIn: '60s' },
    }),
    TypeOrmModule.forFeature([UserRepository]),
  ],
  providers: [AuthService, LocalStrategy, JwtStrategy],
  exports: [AuthService],
})
export class AuthModule {}

 JwtModule을 통해 기본적인 설정 값을 등록했습니다.

 

secret 서명 값은 주요한 키 값이기 때문에 유출에 주의해야 합니다.

 

- constants.ts

export const jwtConstants = {
  secret: 'secretKey',
};

 

지난 시간에 로그인 기능을 구현한 AuthService 부분에 로그인 성공 시 Jwt 토큰을 생성 후 클라이언트에게 반환하도록 코드를 수정하겠습니다.

 

- AuthService

import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { InjectRepository } from '@nestjs/typeorm';
import { UserRepository } from 'src/repository/user.repository';

@Injectable()
export class AuthService {
  constructor(
    @InjectRepository(UserRepository)
    private readonly userRepository: UserRepository,
    private readonly jwtService: JwtService,
  ) {}

  /**
   * @author Ryan
   * @description 단일 유저 조회
   *
   * @param user_id 유저 아이디
   * @param password 유저 비밀번호
   * @returns User
   */
  async validateUser(user_id: string, password: string): Promise<any> {
    console.log('AuthService');

    const user = await this.userRepository.findByLogin(user_id, password);

    //사용자가 요청한 비밀번호와 DB에서 조회한 비밀번호 일치여부 검사
    if (user && user.password === password) {
      const { password, ...result } = user;

      //유저 정보를 통해 토큰 값을 생성
      const accessToken = await this.jwtService.sign(result);

      //토큰 값을 추가한다.
      result['token'] = accessToken;

      //비밀번호를 제외하고 유저 정보를 반환
      return result;
    }
    return null;
  }
}

 

이제 로그인 시 Jwt 토큰 값을 클라이언트는 받을 수 있게 됐습니다.

 

 

토큰 값

 

 

위와 같이 설정을 완료하셨다면 이제 Guards에 등록해서 사용해보겠습니다.

전체 유저 조회 라우터에 Guards를 등록해 "검증된 유저만 접근 가능"하게 설정하겠습니다. 

 

  /**
   * @author Ryan
   * @description 전체 유저 조회
   */
  @UseGuards(JwtAuthGuard)
  @Get('/user_all')
  getUserAll(): Promise<User[]> {
    return this.userService.getUserAll();
  }

 

@UseGuards에 JwtAuthGuard 전략을 등록하면 끝입니다. 이제 실제 요청을 해보겠습니다.

 

토큰 값을 설정하지 않고 요청 시 허가받지 않아 Error가 반환받았습니다.

 

이제 토큰 값을 설정해서 요청해 보겠습니다.

 

 

전달받은 토큰 값을 설정해 서버에 요청한 결과 우리가 원하는 결과 값을 받는 걸 확인하실 수 있습니다.

소스 저장소

github: https://github.com/Ryan-Sin/Node_Nest/tree/v8

 

GitHub - Ryan-Sin/Node_Nest

Contribute to Ryan-Sin/Node_Nest development by creating an account on GitHub.

github.com