NestJs) Authentication

songtofu·2022년 9월 23일
0

Passport

passport에는 다양한 인증 메커니즘을 구현하는 전략이 있다. 개념은 단순하지만 선택할 수 있는 passport 전략 세트는 다양함. passport는 이런 다양한 단계를 표준 패턴으로 추상화하고 @nestjs/passport모듈은 이 패턴을 익숙한 Nest 구성으로 래핑하고 표준화한다.

  • passport는 이러한 과정을 실행한다.
    1. "credentials(자격 증명)"-> 사용자 이름/비밀번호, JSON 웹 토큰(JWT) 또는 ID제공자의 ID 토큰)을 확인해 사용자를 인증한다.

    1. 인증 상태 관리 (JWT와 같은 이식 가능한 토큰 발행 또는 Express 세션 생성)
    2. 인증된 사용자에 대한 정보를 경로 처리기에서 추가로 사용하기 위해 개체에 첨부한다.

    예제

    인증 요구 사항

  • 요구 사항을 구체화. 사용자 이름과 비밀번호로 인증을 시작한다. 인증되면 서버는 인증을 증명하기 위한 후속 요청에서 인증 헤더의 전달자 토큰으로 보낼 수 있는 JWT를 발행한다. 또한 유효한 JWT를 포함하는 요청에만 엑세스 할 수 있는 보호된 경로를 생성한다.

    첫 번째 요구사항(사용자 인증)

    필요한 패키지 설치. Passport는 사용 사례의 이 부분에 대한 요구 사항에 맞는 사용자 이름/암호 인증 매커니즘을 구현하는 Passport-local 이라는 전략을 제공한다.

    $ npm install --save @nestjs/passport passport passport-local
    $ npm install --save-dev @types/passport-local
 > Notice
 	선택한 Passport 전략에는 항상 `@nestjs/passport`및`passport`패키지가 필요하다. 그런 다음 구축 중인 특정 인증 전략을 구현하는 전략별 패키지(ex. `passport-jwt`)를 설치해야한다. 또한 TypeScript 코드를 작성하는 동안 도움을 제공하는 `passport-local`와 함께 위에 표시된 것 처럼 모든 Passport 전략에 대한 유형 정의를 설치할 수 도 있다. (ex.@types/passport-local)
 
 ## passport 전략 구현
  - Passport 전략에 사용되는 프로세스의 개요부터 시작. Passport를 그 자체로 미니 프레임워크로 생각하면 도움이 된다. 프레임워크의 장점은 인증 프로세스를 구현 중인 전략에 따라 사용자가 지정하는 몇 가지 기본단계로 추상화한다는 것이다. Passport가 적절한 시간에 호출하는 콜백 함수 형태로 사용자 매개변수(일반 JSON 개체) 및 사용자 정의 코드를 제공하여 구성하기 때문에 프레임워크와 같다. 모듈은 `@nestjs/passport` 이 프레임워크를 Nest 스타일 패키지로 래핑하여 Nest 애플리케이션에 쉽게 통합할 수 있다. 아래에서 사용할 것이지만 먼저 vanilla Passport(NodeJs에 기본제공되는 passport로 추정)`nestjs/passport`가 작동하는 방식을 살펴 보겠다.
  ### vanilla Passport 작동방식
  - vanilla Passport에서의 두가지 제공 전략
  	1. 해당 전략에 특정한 옵션 집합. 예를 들어, JWT 전략에서 토큰에 서명하기 위한 secret 을 제공할 수 있다.
    2. Passport에게 사용자 저장소(사용자 계정을 관리하는 곳)와 상호작용하는 방법을 알려주는 "verify callback"이다. 여기에서 사용자의 존재여부(또는 새 사용자 생성)와 자격 증명이 유효한지 확인한다. Passport 라이브러리는 이 콜백이 유효성 검사가 성공하면 전체 사용자를 반환하거나 실패하면 null을 반환할 것으로 예상한다.(실패는 사용자를 찾을 수 없거나 passport-local의 경우 비밀번호가 일치하지 않는 것으로 정의된다.)
    
 이를 사용하면 클래스 `@nestjs/passport`를 확장하여 Passport 전략을 구성할 수 있습니다. 하위 클래스에서 메서드를 호출하고 선택적으로 옵션 개체를 `PassportStrategy` 전달하여 vanilla Passport 작동방식 1번 전략 옵션을 전달합니다. 하위 클래스에서 메서드를 `super()`구현하여 vanilla Passport 작동방식 2번 확인 콜백을 제공합니다.
 
 `AuthModule`을 위한 `AuthService`를 생성합니다.

$ nest g module auth
$ nest g service auth

 이를 구현할 때 `AuthService` User 작업을 캡슐화하는 것이 유용하기 때문에 `UserService`와 `UserModule`을 생성한다.

$ nest g module users
$ nest g service users


```user//user.service.ts
import { Injectable } from '@nestjs/common';

// This should be a real class/interface representing a user entity
export type User = any;

@Injectable()
export class UserService {
  private readonly user = [
    {
      userId: 1,
      username: 'john',
      password: 'changeme',
    },
    {
      userId: 2,
      username: 'maria',
      password: 'guess',
    },
  ];

  async findOne(username: string): Promise<User | undefined> {
    return this.user.find(user => user.username === username);
  }
}

UserModule에 필요한 변경 사항은 이 모듈을 외부에서 볼 수 있도록 데코레이터 UserService의 export 배열에 추가하는 것이다.

import { Module } from '@nestjs/common';
import { UsersService } from './users.service';

@Module({
 providers: [UsersService],
 exports: [UsersService],
})
export class UsersModule {}

AuthService는 사용자를 검색하고 암호를 확인하는 작업을 가지고 있다. validateUser()이를 위해 매서드를 만듭니다. 아래 코드에서는 편리한 ES6 스프레드 연산자를 사용하여 변환하기 전에 사용자 개체에서 암호 속성을 제거합니다. validateUser() 잠시 후 Passport 로컬 전략에서 매서드를 호출한다.

import { Injectable } from '@nestjs/common';
import { UsersService } from '../users/users.service';

@Injectable()
export class AuthService {
  constructor(private usersService: UsersService) {}

  async validateUser(username: string, pass: string): Promise<any> {
    const user = await this.usersService.findOne(username);
    if (user && user.password === pass) {
      const { password, ...result } = user;
      return result;
    }
    return null;
  }
}

경고!
실제 응용 프로그램에서는 암호를 일반 텍스트로 저장하지 않는다. 단방향 해시 알고리즘과 함께 bcrypt와 같은 라이브러리를 사용합니다. 이 접근 방식을 사용하면 해시된 암호만 저장한 다음 저장된 암호를 수신하고 이를 해시된 버전과 비교하므로 사용자 암호를 일반 텍스트로 저장하거나 노출하지 않는다.

이제 AuthModule에서 UserModule을 가져오도록 한다.

import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { UsersModule } from '../users/users.module';

@Module({
imports: [UsersModule],
providers: [AuthService],
})
export class AuthModule {}

Passport Local 구현

  • passportLocal이란? username과 password를 쓰는 로그인 방식을 말한다. (strategy)
    이제 Passport local 인증전략을 구현할 수 있다. auth 폴더에 local.strategy.ts라는 파일을 생성 한 후 다음 코드를 추가한다.
import { Strategy } from 'passport-local';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthService } from './auth.service';

@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
  constructor(private authService: AuthService) {
    super();
  }

  async validate(username: string, password: string): Promise<any> {
    const user = await this.authService.validateUser(username, password);
    if (!user) {
      throw new UnauthorizedException();
    }
    return user;
  }
}

Passport-local을 사용하는 경우에는 구성 옵션이 없으므로 생성자 super()는 옵션 객체 없이 단순하게 호출만 합니다.

힌트!
super() 에서 passport strategy 동작을 정의하기 위해 options 객체를 전달 할 수 있다. 이예제에서 passport-local strategy은 기본적으로 요청 본문에 호출된 속성(username, password)을 예상한다. options 객체를 전달하여 다른 속성 이름을 지정한다. (ex. super({uesrnmaeField: 'email'}) 자세한 내용은 passport 문서를 참고하세요

또한 validate() 메서드를 구현했습니다. 각 전략에 대해 Passport는 적절한 전략별 매개 변수 집합을 사용하여 확인 기능(@nestjs/passportvalidate() 메서드로 구현)을 호출합니다. 로컬 전략의 경우 Passport는 validate() 메서드에 validate(사용자 이름: 문자열, 암호: 문자열): any 서명을 사용할 것으로 예상됩니다.

profile
읽으면 머리에 안들어와서 직접 쓰는 중. 잘못된 부분 지적 대환영

0개의 댓글