230502 화 | OBO : CustomAuthGuard로 가드 동적선택 구현

UI SEOK YU·2023년 5월 2일
0

Daily

목록 보기
1/2

다양한 로그인 방식 지원하기

소셜로그인 추가에 따른 동적 가드구현

  • 로컬로그인, 소셜로그인 등 다양한 로그인 방식마다 컨트롤러를 구현하는 것이 비효율적이라 생각했다.
  • 따라서 각 가드에서는 해당계정이 존재하는지만 확인하고, 다시 body의 값을 서비스 로직으로 전달하여 해당 계정의 userId를 받아오는 공통로직을 작성했다. 그 후, api 엔드포인트에 따라 적절한 가드가 동적으로 적용되도록 구성했다.
  • ( 로컬의 경우 인증하면서 해당 유저의 userId를 받아올 수 있지만, 전체적인 로직의 통일을 위해 DB에 있는지만 먼저 확인하고, userId를 받아오는 것은 다른 로그인 방식과 같은 로직을 이용하게 했다. )
@UseGuards(DynamicAuthGuard)
  @Post('login/:method')
  async localLogin(
    @Body()
    userlocalLoginInboundPortInput: UserLoginInboundPortInputDto,
    @Res() res: Response,
  ): Promise<UserLoginInboundPortOutputDto> {
    // 구글이나 로컬에서 사용자가 DB에 있음을 확인 후, 해당 email의 Userid를 가져온다.
    const jwt = await this.userLoginInboundPort.login(
      userlocalLoginInboundPortInput,
    );
    // 가져온 id를 가지고 jwt 토큰을 발급한다.
    res.setHeader('Authorization', 'Bearer ' + jwt.accessToken);
    res.json(jwt.accessToken);
    return { accessToken: jwt.accessToken };
  }

이를 위해서는 이 코드에서 DynamicAuthGuard를 구현해야 한다.
가드는 보통 AuthGuard('something') 형태로 생성할 수 있지만,
동적선택이 필요하므로, 커스텀 된 가드가 필요했다.
따라서 가드의 모체인 CanActivate를 상속받아 내부에 조건문을 구현했다.



아래 두 코드는 구현하려했던 DynamicAuthGuard 의 내용이다.

// 1번
import {
  CanActivate,
  ExecutionContext,
  Injectable,
} from '@nestjs/common';
import { LocalAuthGuard } from './local/guard/auth.local.guard';
import { Observable } from 'rxjs';
import { GoogleAuthGuard } from './google/guard/auth.google.guard';

@Injectable()
export class DynamicAuthGuard implements CanActivate {
  constructor(
    private readonly localAuthGuard: LocalAuthGuard,
    private readonly googleAuthGuard: GoogleAuthGuard,
  ) {}

  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const guard = this.getGuard(context);
    return guard ? guard.canActivate(context) : false;
  }

  private getGuard(context: ExecutionContext): CanActivate {
    const request = context.switchToHttp().getRequest();
    switch (request.path.split('/').pop()) {
      case 'local':
        return this.localAuthGuard;
      case 'google':
        return this.googleAuthGuard;
      default:
        return null;
    }
  }
}
// 2번
import { LocalAuthGuard } from './local/guard/auth.local.guard';
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Observable } from 'rxjs';
import { GoogleAuthGuard } from './google/guard/auth.google.guard';

@Injectable()
export class DynamicAuthGuard implements CanActivate {
  constructor() {}

  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const guard = this.getGuard(context);
    return guard ? guard.canActivate(context) : false;
  }

  private getGuard(context: ExecutionContext): CanActivate {
    const request = context.switchToHttp().getRequest();

    switch (request.params.path) {
      case 'local':
        return new LocalAuthGuard();
      case 'google':
        return new GoogleAuthGuard();
      default:
        return null;
    }
  }
}

두 코드의 차이는 무엇인가?
1번 코드로 했을 때에는, 이 DynamicAuthGuard 를 사용하는 모듈에 LocalAuthGuard와 GoogleAuthGuard를 주입하고나서 사용해야 했다.

그러나 2번 코드는 사용하는 모듈에 가드를 주입하지 않아도 사용이 가능하다. 왜 하나의 코드는 가드의 주입이 필요하고 다른 하나는 그렇지 않은가?

두 코드의 주요 차이점은 DynamicAuthGuard의 생성자에서 다른 가드를 주입 받는지 여부이다.

  • 첫 번째 코드에서는 NestJS의 의존성 주입(Dependency Injection, DI) 시스템을 사용하여 LocalAuthGuard와 GoogleAuthGuard를 DynamicAuthGuard에 주입하고 있다. 이렇게 하면 DynamicAuthGuard는 이미 생성된 LocalAuthGuard와 GoogleAuthGuard 인스턴스를 사용할 수 있다. 하지만 이 방식을 사용하려면 DynamicAuthGuard를 사용하는 모듈에 LocalAuthGuard와 GoogleAuthGuard를 제공해야 한다.

  • 두 번째 코드에서는 DynamicAuthGuard 내부에서 '직접' LocalAuthGuard와 GoogleAuthGuard 인스턴스를 생성하고 있다. 이 경우에는 DynamicAuthGuard를 사용하는 모듈에서 이들 가드를 제공할 필요가 없다. 이 방식의 단점은 LocalAuthGuard와 GoogleAuthGuard가 다른 의존성을 필요로 하는 경우, 이들 의존성을 직접 관리해야 한다는 것.

따라서 나는 싱글톤 패턴의 위배를 막기 위하여, 첫번째 방식을 통해 가드 클래스들을 DynamicAuthGuard에 직접 주입하였다.
그리고 DynamicAuthGuard를 다른 모듈에서 사용하므로써 하나의 인스턴스를 재활용하는 구조를 채택했다.

0개의 댓글