JWT 생성부터 권한관리까지(3) __(Nest + JWT + TypeORM) __권한(Role)관리 #2(가드 생성)

DatQueue·2022년 11월 9일
2
post-thumbnail

시작하기에 앞서

3개의 포스팅을 거치면서 우린 JWT 생성부터 권한관리까지의 내용을 진행중에 있고, 이전 포스팅에선 권한 부여를 하기에 앞서 db에 사용자의 권한을 관리하기 위한 테이블을 생성하고 기존 유저의 데이터를 담은 테이블과 JOIN 하는 작업까지 진행하였다.

이번 포스팅에선 권한 부여를 위한 RolesGuard를 생성해 볼 것이다. 이전에 생성해보았던 AuthGuard보단 더 추가할 로직이 많을 것으로 예상된다. 그럼 한번 진행해보자.

Guard 생성 __ RolesGuard


Payload 수정

기존 페이로드 인터페이스에 우리가 User 엔티티에 추가해 준 authorities를 추가해 준다.

// payload.interface.ts

export interface Payload {
  id: number;
  username: string;
  authorities?: any[];  // authorities가 있을 수도 있고, 그렇지 않을수도 있으므로 "?"이용
}

Role Type 생성


앞서 생성하였던 user_authority테이블이다. 역할을 담고 있는 authority_name엔 두 가지의 역할인 ROLE_USERROLE_ADMIN이 있다.

나중에 컨트롤러에 해당 Role을 적용시키기에 앞서 Role의 타입을 미리 선언해 줄 필요가 있다. 어떤 타입이 가능한 지 지정해 주는 것이다.

우리는 Role Type을 TS의 enum 객체를 사용하여 지정해 줄 것이다. enum에 대한 설명은 생략하겠다. 생성할 Role Type은 DB에 정의된 것과 꼭 동일하게 만들어준다.

// role-type.ts

export enum RoleType {
  USER = 'ROLE_USER',
  ADMIN = 'ROLE_ADMIN',
}

Decorator 생성

컨트롤러에서 메뉴의 사용 권한을 표시해 주기 위해 데코레이터를 생성할 것이다. 즉, 우리가 바로 위에서 지정해 준 RoleType을 읽어올 수 있는 데코레이터를 만들어 주는 것이다.

// role.decorator.ts

import { SetMetadata } from "@nestjs/common";
import { RoleType } from "../role-type";

export const Roles = (...roles: RoleType[]): any => SetMetadata('roles', roles);

즉, 위와 같은 데코레이터는 나중에 컨트롤러에서 @Roles와 같은 형식으로 POST 방식에 쓰일 것이다.

본인도 그렇고, Nest의 구조를 처음 접한 사람에게는 갑자기 Roles 데코레이터를 만드는 것이고 어떠한 역할을 가지냐 에 관해 의문을 품을 것이다. 익숙치 않기 때문이다.

해당 데코레이터를 생성하기 까진 일련의 단계가 존재한다. 우리는 그것을 Nest 공식 문서를 통해 직접 확인할 수 있다.

공식 문서는 전부 영어로 되어있어서 이해하는데 어려움이 있을 수 있다. 그래서 RolesGuard 생성부터 Roles 데코레이터 생성까지 그 과정을 번역하여 우리의 진행 과정에 앞서 알아보고자 한다.


해당 내용은 개별의 포스팅으로 진행하고자 한다. ⬇⬇⬇ 아래 포스팅 참고
Role Guard 생성 __공식문서 번역



RolesGuard 생성

위에 ⬆⬆ 걸어둔 "Role Guard 생성 __공식문서 번역" 을 먼저 보고 해당 내용으로 넘어가는 것을 추천한다. 공식문서의 코드 베이스를 바탕으로 진행이 될 것이다.

// role.guard.ts

import { CanActivate, ExecutionContext, Injectable } from "@nestjs/common";
import { Reflector } from "@nestjs/core";
import { Observable } from "rxjs";
import { User } from "../entity/user.entity";

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private readonly reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
      const roles = this.reflector.get<string[]> ('roles', context.getHandler());
	
      if(!roles) {  // roles가 아니면 true를 리턴하고 진행한다.
        return true;
      }

      const request = context.switchToHttp().getRequest();
      const user = request.user as User;

      return user && user.authorities && user.authorities.some(role => roles.includes(role));
  }
}

공식문서에서도 나와있듯이 constructor 내부에 프라이빗 생성자로 담은 Reflector를 통해 메타데이터를 런타임에서 가져오도록 한다.

우린 Reflector를 통해 roles를 string 타입의 배열 값으로 불러올 수 있다.
만약 roles가 지정이 되있다면 실행 컨텍스트에서 요청 객체인request를 불러온다.

request는 주의깊게 볼 필요가 있다. 우리가 지난 포스팅 "토큰 검증 - AuthGuard를 컨트롤러에 적용" 부분에서

@Get('/authenticate')
@UseGuards(AuthGuard)
isAuthenticated(@Req() req: Request): any {
  const user: any = req.user;
  return user;
}

isAuthenticated의 파라미터로 Request객체를 받고, 해당 요청 객체의 user값을 받는 작업을 수행해 보았었다. 이렇게 반환된 user는 "토큰 인증이 확인" 된 객체이다. 조금 더 자세히 말하면 우리가 앞서 작성한 JwtStrategy의 메서드 validate()에서 반환하였던

// passport.jtw.strategy.ts
// ~~~~

async validate(payload: Payload, done: VerifiedCallback): Promise<any> {
    const user = await this.authService.tokenValidateUser(payload);
    if(!user) {
      return done(new UnauthorizedException({message: 'user does not exist'}), false)
    }
    return done(null, user);
  }

user 객체인 것이다. 앞서 컨트롤러에서 const user: any = req.user을 통해 req.user의 타입이 any였다면 Role Guard에서 아래와 같이

const user = request.user as User

User 타입으로 "타입 단언"을 해줌으로써 타입시스템의 장점을 가질 수 있게 된다.

그렇게 최종 RolesGuard는 아래와 같은 값들을 리턴하는데

return user && user.authorities && user.authorities.some(role => roles.includes(role));

User를 타입으로 받는 user객체와 User 엔티티 내에 정의해 준 authorities, 그리고 javascript의 some()메서드를 사용하여 authorities배열이 실행 컨텍스트에서 얻게 되는 roles를 포함하고 있는지에 관한 불리언 값을 리턴 받는다.


💨 참고 !

  • Array.prototype.some()
    : some() 메서드는 배열 안의 어떤 요소라도 주어진 판별 함수를 통과하는지 테스트한다.

    인자로는 callback을 받고 callback이 어떤 배열 요소에 대해서라도 참인 값을 반환하는 경우 true , 그 외엔 false를 가진다.



생각정리 및 다음 포스팅 예고

이번 포스팅에선 권한 부여를 위한 RolesGuard를 구현하였고 어떠한 원리로 원하는 user 객체를 전달하고 반환하는지에 대해 알아보았다.

지난 포스팅들에 비해 짧게 작성하였는데 이번 포스팅에서 사실 가장 중요한 부분은 오히려 이번 포스팅의 작성물이 아니라 내용 중간에 링크로 걸어준 "Nest 공식 문서 번역 __RolesGuard" 일지도 모른다. 꼭 먼저 보고 오길 추천한다.

다음 포스팅에선 우리가 생성한 RolesGuard를 본격적으로 사용하기 위해 서비스와 컨트롤러에 적용하는 과정을 가져보도록 하자.

profile
You better cool it off before you burn it out / 티스토리(Kotlin, Android): https://nemoo-dev.tistory.com

0개의 댓글