kakasoo

한 NestJS 서버에서 여러 Strategy 구현하기 본문

프로그래밍/NestJS

한 NestJS 서버에서 여러 Strategy 구현하기

카카수(kakasoo) 2022. 7. 17. 12:49
반응형

 

서비스를 구현하다보면 당연히 여러 사용자가 존재하게 된다. 이 사용자라는 표현은, 그냥 내 임의로 부르는 호칭인데, 말하자면 유저의 성격이다. 예컨대 커머스라고 한다면 구매자와 판매자, 관리자가 있을 수 있고, 예약 서비스를 만든다면 식당 주인과 손님이라는 관계가 생길 것이다. 대개 서비스라고 함은 하나의 사용자만 존재할 리 없고, 하다 못해 관리자를 포함해 둘 이상의 사용자가 반드시 존재할 수 밖에 없다.

문제는 여기서 발생한다.

"서로 다른 사용자를 어떻게 한 서버 내에서 인증하는가?"

 

PassportStrategy에 이름 설정하기

우리가 사용하는 Gaurd들에 대해서 다시 고찰해볼 필요가 있다. Guard들은 어떻게 자신에게 할당된 전략을, 알아서 찾아가는 걸까?

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

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

 

간단하게 구현한 코드를 보면, JwtGuard는 AuthGuard에 'jwt' 라는 string을 파라미터로 넘겨준 어떤 클래스를 상속받고 있다. 이것을 볼 때 결국 JwtGuard라고 함은, AuthGuard('jwt')와 동치임을 알 수 있다. 그저, 작성하기 편하라고 네이밍을 생략한 클래스에 불과하다는 뜻이다.

그러나, 사실 이 'jwt' 라는 스트링이야말로 Guard의 동작 방식을 결정하는 요소이다.

 

import { Type } from '../interfaces';
export declare function PassportStrategy<T extends Type<any> = any>(Strategy: T, name?: string | undefined): {
    new (...args: any[]): InstanceType<T>;
};

 

사실 PassportStrategy는 이름을 파라미터로 받고 있다. 엄밀히 말해, Strategy들은 이름을 생성자 함수의 파라미터로 받는다. 그런 점에서 PassportStrategy 역시 문서를 충실하게 따른, 규칙이 있는 셈이다. 우리는 전략을 설정할 때 별도의 이름을 넣어주고 있진 않지만, Default로 설정된 이름들이 들어가서 Guard가 찾아갈 수 있도록 길라잡이 역할을 해준 것이다. 따라서 이 이름을 우리가 원하는 대로 바꿈으로써, 우리는 Guard들이 각기 다른 전략을 찾아가게 만들 수 있다.

 

똑같은 Strategy 여러 개 만들기

import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
    constructor(private readonly configService: ConfigService) {
        super({
            jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
            ignoreExpiration: false,
            secretOrKey: configService.get('PRIVATE_KEY'),
        });
    }

    async validate(payload: any) {
        const { id } = payload;
        return { id };
    }
}

 

이러한 느낌의 전략이 있다고 해보자. 여기서 생성자와, validate 함수에는 바뀔 게 없다. 이제 파라미터만 바꿔주면 된다. 이름을 직접 지정해보자.

 

import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';

@Injectable()
export class JwtOneStrategy extends PassportStrategy(Strategy, 'jwt-one') {
    constructor(private readonly configService: ConfigService) {
        super({
            jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
            ignoreExpiration: false,
            secretOrKey: configService.get('PRIVATE_KEY'),
        });
    }

    async validate(payload: any) {
        const { id } = payload;
        return { id };
    }
}

 

이번에는 'jwt-one' 이라는 이름으로 변경했다. 이로 인해 기존의 AuthGuard('jwt') 또는 JwtAuthGuard는 먹통이 되었을 것이다. 이러한 방식으로 JwtOne뿐만 아니라 two, three, four 다양한 Strategy와 Guard들을 만들 수 있다.

 

결론

결국 구매자와 판매자, 관리자, 또는 그 외 역할 군에 따라 각기 다른 전략을 세울 수 있는 것이다. 사실, 사용자마다 서버가 다르다면 그게 가장 낫다고 생각하지만, 인원이 적은 스타트업에서, 서버를 분리해서 관리하는 것은 비용을 늘리는 셈이 된다. 사람이 많다면 적당한 수로 서버를 쪼개, 각기 자기 범위 내를 커버하게 하면 되지만, 실제로는 한 사람이 2~3개 이상의 서버를 관리해야 할 수도 있다.

 

우리가 Entity를 npm에 private하게 올려서, 각 서버마다 entity들을 install하고, 버전을 관리하면서, 서버마다 1명 이상의 서버 개발자를 배정할 수 있는가. 그러면 인증 방식에 대해 고민할 필요가 없다. 하지만 스타트업과 같이, 개발자 인력이 부족한 경우라면 분리하기 보다 합치는 게 더 경제적일 수 밖에 없고, 한 서버 내에서 다양한 인증 전략을 세우는 것은 필연적이다.

반응형