작은 도서관

사용 라이브러리

"@nestjs/common": "^8.0.0",
"@nestjs/core": "^8.0.0",

개요

  • 데코레이터 구현
  • 유저에 rules 추가
  • 로그인과 토큰 발급
  • 처리 구현

데코레이터 구현

먼저, 컨트롤러에 사용할 데코레이터를 구현할 필요가 있다.

그 전에, 서버 내에서 사용할 역할을 나타내는 역할의 열거형을 만들어야한다.

src/modules 아래에 /roles 폴더를 만들고 다음을 작성한다.

// roles.enum.ts

export enum Role {
  User = 'user',
  Admin = 'admin',
}

필요한 대로 추가해도 되지만, 일단은 예시니까 user와 admin만 추가한다.

// roles.decorator.ts

import { SetMetadata } from '@nestjs/common';

import { Role } from './role.enum';

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

대충 열거형을 나열해서 메타데이터를 지정해 데코레이터로 만들어주는 코드라고 이해하면 된다.

이제 다음과 같이 사용할 수 있다.

// 컨트롤러 일부
@Roles(Role.Admin)
  async find(): Promise<User> {
    return this.userService.find(id);
 }

유저 정보는 아무나 요청하면 안되니 Admin 역할을 가진 유저만 요청할 수 있게 한다.

유저에 rules 추가

rules는 배열로 추가한다. 해당 배열 안에 필요한 권한이 포함되어 있다면 통과시키고, 혹은 에러를 발생시킨다.

// user.schema.ts

import { Role } from 'src/modules/roles/role.enum';

export class User {
  @Prop({
    type: Array,
    required: true,
    default: ['User'],
  })
  roles: Role[];
}

// 생략

만약 권한이 부여되어있지 않다면 유저로 간주한다.

로그인과 토큰 발급

auth라는 모듈을 만들기 전에, 해당 모듈에선 jwtservice와 passpost라는 라이브러리를 사용한다. 

// auth.module.ts

@Module({
  controllers: [],
  providers: [],
  imports: [
    PassportModule,
    JwtModule.register({
      secret: 'your-secret-key-here',
      signOptions: { expiresIn: '3600s' },
    }),
  ],
  exports: [],
})

따라서 먼저 이런 모듈을 세팅한 뒤 서비스를 만든다.

 // auth.service.ts
 
 async ValidateUser(content): Promise<any> {
    try {
      const user = await this.userService.find(content.id);

      if (user.password === content.password) {
        const cookie = await this.login({ id: content.id });

        return cookie;
      }
    } catch (error) {
      throw new UnauthorizedException();
    }

    return null;
  }
  
  async login(user: any) {
    return this.jwtService.sign(user);
  }

  async verifyToken(token: string) {
    return this.jwtService.verify(token);
  }

content에 id와 password 정보를 넣어서 보내면 db에 저장되어있는 비밀번호와 비교하면 같을 경우 토큰을 발급하고, 다를경우 오류를 발생시키는 서비스이다.

verifyToken의 경우는 나중에 권한을 처리할때 사용된다.

이 토큰을 프론트엔드단에서 Bearer토큰으로 등록하여 사용하면 된다.

 

처리 구현

// roles.guard.ts

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(
    private reflector: Reflector,
    private userservice: UsersService,
    private authService: AuthService,
  ) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const requiredRoles = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);
    if (!requiredRoles) {
      return true;
    }
    const { headers } = context.switchToHttp().getRequest();

    if (headers.authorization?.startsWith('Bearer ')) {
      const token = headers.authorization.substring(7);
      const verified = await this.authService.verifyToken(token);
      const user = await this.userservice.find(verified.id);

      return requiredRoles.some((role) => user.roles?.includes(role));
    }

    return null;
  }
}

Role에서 사용하는 가드이다. 위에서 @Roles()로 보호받는 컨트롤러는 무조건 이 코드를 거치게 된다.

받은 정보의 헤더에서 토큰을 가져와 해당 토큰에 적혀있는 id를 토대로 authService에서 만들었던 verifyToken을 실행하고, 유저 정보를 넘겨받아 권한이 있는지 확인한다.

권한이 있는 유저라면 그대로 통과되고, 없는 유저라면 forbidden resources를 반환받는다.

profile

작은 도서관

@Flrea

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!