[Node.js_4기] TIL : Nest.js 2_인증과 인가 (24/03/14)

2024. 3. 14. 21:02공부/내배캠 TIL

목차

 

1. 학습 내용

2. 내용 정리

3. 예제

4. 생각 정리

 

1. 학습 내용 

 

  • 인증과 인가의 차이를 명확하게 알고 Nest.js에서 이를 구현할 수 있습니다.
  • 커스텀 데코레이터를 사용하여 컨트롤러를 더욱 더 세부적으로 커스터마이징 할 수 있습니다.
  • Nest.js에서 가드를 사용하여 권한에 따른 라우트 접근 제어를 할 수 있습니다.

 

2. 내용 정리 

 

01. Nest.js에서의 인증

1) 인증 : 사용자를 증명하는 절차

  • API 호출마다 ID와 비밀번호를 넘기는 가장 원초적인 인증
  • JWT를 통한 인증(표준)

2) JWT 발급 구현

  • loginUser
    • 로그인 성공 시 JWT를 발급해야 합니다.
  • createUser
    • 회원가입을 성공하면 JWT를 발급해야 합니다.
  • checkUser
    • JWT가 검증된 유저가 부를 수 있는 임시 함수.(유저 ID로 유저가 있는지 체크하는 내부 함수)

02. Nest.js에서의 인가 (1) - 개념, 커스텀 데코레이터

1) 인가 : 권한을 확인 받는 절차

  • 인증된 유저로 API를 호출할 수 있다.
  • 특정 API를 호출할 때, 서버는 사용자가 API를 호출할 수 있는 권한이 있는지 체크하는 과정이 필요하다.

2) 커스텀 데코레이터

  • 데코레이터
    • 클래스나 함수와 같은 곳에 메타데이터를 추가하는 방법을 제공하는 선언 방법
    • 1. 코드에 추가적인 정보를 제공, 2. 실행 시점에 코드의 동작 방법이 제공
  • 커스텀 데코레이터
    • 원하는 대로 동작을 정의한 데코레이터
    • 추가적인 로직을 삽입하거나 특정 정보를 추출하거나 변경할 수 있게된다.
    • 필요성
      • 유저 식별 데코레이터 : 요청에서 사용자 정보를 추출하고 라우터 핸들러에 전달 가능
      • 유저 접근 제어 데코레이터 : 사용자의 역할을 기반으로 특정 엔드포인트에 대한 접근 제어

03. Nest.js에서의 인가(2) - 가드

1) Guard

  • Nest.js에서 특정 라우트에 대한 접근을 제어하는 역할
  • 작동 방식
    • canActivate 메서드(라우터 접근을 허용/거부 결정)를 구현하는 클래스로 구성
  • 생성 방법
    • 커맨드로 직접 생성 가능 - nest g guard level

 

 

 

3. 예제 

 

01_2) JWT 발급 구현

import { JwtService } from '@nestjs/jwt';

@Injectable()
export class UserService {
  constructor(
    @InjectRepository(User) private userRepository: Repository<User>,
    private jwtService: JwtService, // JWT 토큰 생성을 위해 주입한 서비스
  ) {}
  
   // 로그인 로직이 끝난 후
   // JWT 토큰 생성
    const payload = { id: user.id };
    const accessToken = await this.jwtService.signAsync(payload);
    return accessToken;
  }
  // 회원가입 로직이 끝난 후
  // JWT 토큰 생성
    const payload = { id: newUser.id };
    const accessToken = await this.jwtService.signAsync(payload);
    return accessToken;
  }

}

01_03) JWT 검증 구현

// 생략
import { JwtService } from '@nestjs/jwt';

@Injectable()
export class AuthMiddleware implements NestMiddleware {
  constructor(private jwtService: JwtService) {}

  // eslint-disable-next-line @typescript-eslint/ban-types
  async use(req: any, res: any, next: Function) {
    const authHeader = req.headers.authorization;

    if (!authHeader) {
      throw new UnauthorizedException('JWT 토큰을 찾을 수 없습니다!');
    }

    let token: string;
    try {
      token = authHeader.split(' ')[1];
      const payload = await this.jwtService.verify(token);
      req.user = payload;
      next();
    } catch (err) {
      throw new UnauthorizedException(`JWT 토큰이 올바르지 않습니다: ${token}`);
    }
  }
}
  • 클라이언트가 헤더의 Authorization 필드로 Barrer {JWT}를 보내면 AuthMiddleware가 JWT를 파싱하여 특정 유저를 인증한다.

 

02_4) 커스텀 데코레이터 코드 살펴보기

import { createParamDecorator, ExecutionContext } from "@nestjs/common";

export const UserInfo = createParamDecorator(
  (data: unknown, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest();
    if (request.user) {
      return request.user;
    }
    return null;
  }
);

@UserInfo : JWT 인증이 완료된 유저에 한해서 유저 데이터를 갖고오게 하는 데코레이터

// 적용 예시 
import { Controller, Get } from '@nestjs/common';
import { CurrentUser } from './current-user.decorator';

@Controller('user')
export class UserController {
  @Get()
  getProfile(@UserInfo() user: any) {
    return user;
  }
}

 

 

03_02) Guard 살펴보기

<Guard>

import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common';

@Injectable()
export class LevelGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest();
    const user = request.user;  // user 객체는 { level: number } 형태라고 가정합니다.
    
    if (user && user.level === 1) {
      return true;
    }
    throw new ForbiddenException('접근이 금지되었습니다!');
  }
}
  • LevelGuard라는 이름의 Guard 생성. canActivate 메서드에서 사용자 정보를 가져온다.
  • 조건을 만족한 사용자에게만 접근을 허용 / ForbiddenException을 발생시켜 접근을 거

<컨트롤러에 적용>

import { Controller, Get, UseGuards } from '@nestjs/common';
import { LevelGuard } from './level.guard';

@Controller('admin')
@UseGuards(LevelGuard)
export class AdminController {
  @Get('dashboard')
  getDashboard() {
    return { message: 'Admin Dashboard' };
  }
}
  • LevelGuard는 AdminController에 적용된다. 모든 라우트는 LevelGuard에 의해 보호된다.

 

4. 생각 정리 

 

일단 내용 정리만 해둔 상태로, 다음번 TIL과 개인 프로젝트 회고로 Nest.js TIL을 마무리할 생각입니다.

개인과제를 헤맨 상황이라, 해설영상에 대한 TIL도 작성하면서 필요한 내용을 추가적으로 정리할 예정입니다.

다음 팀 프로젝트 이후, 최종 프로젝트를 진행하게 됩니다.

나 스스로가 마지막까지 열정을 잃지 않고, 마무리할 수 있기를.