새로운 스프린트 기간이 시작되며, 새로운 기능 개발 요건이 들어왔습니다.
요구된 기능은 아래와 같습니다.
1. 기존의 user에 저장된 role을 기반으로 API 접근을 차단하라
// roles.decorator.ts
import { SetMetadata } from "@nestjs/common";
// SetMetadata를 사용해 metadata 저장
export const Roles = (...roles: userRoles[]) => SetMetadata("roles", roles);
// handler.roles.guard.ts
@Injectable()
export class HandlerRolesGuard implements CanActivate {
constructor(
@InjectModel(OrganizationMember.name)
private userModel: Model<userDocument>,
private reflector: Reflector,
) {}
async canActivate(ctx: ExecutionContext) {
const request = ctx.switchToHttp().getRequest();
const userId = request.session.credentials.user._id;
const userRole = await this.getUserRole(userId);
//metadata에 저장한 키를 통해 reflector에서 불러오기
const roles = this.reflector.get<userRoles[]>("roles", ctx.getHandler());
const hasRole = () => roles.includes(userRole);
return userRole && hasRole();
}
private async getUserRole(
userId: string,
): Promise<User["permission"]> {
const user = await this.userModel.findOne({
userId,
});
if (!user)
throw new BadRequestException(
`userId: ${userId} not exist in user`,
);
return user.permission;
}
}
@UseGuards(HandlerRolesGuard)
@Patch("/:userId")
@Roles(UserPermission.ADMIN)
updateUser(
@Body()
updateUserDto: UpdateUserDto,
) {
...
}
여러 권한 체계를 가진 환경이라면, AbstractPermissionGuard를 구현하여 해당 클래스를 상속받아 구체적인 권한 검사를 위한 가드를 만들 수 있습니다.
해당 클래스는 protected abstract 키워드를 사용하여 자식 클래스에서 반드시 구현되어야 하는 일부 메서드와 속성을 정의합니다.
export abstract class AbstractPermissionGuard implements CanActivate {
protected abstract readonly reflector: Reflector;
protected abstract get Service(): Service; // AbstractPermissionGuard를 상속받는 class에서 필요한 의존성을 주입할 수 있다
constructor() {}
protected async validateRole({ request, user, ctx }: AuthorizePayload) {
// 공통적으로 사용될 수 있는 role validate 로직 구현
}
async canActivate(ctx: ExecutionContext) {
const request = ctx.switchToHttp().getRequest();
const user = request.session.credentials.user;
return this.authorize({ request, user, ctx });
}
// AbstractPermissionGuard를 상속받는 class에서 필요에 맞게 authorize를 구현합니다.
protected abstract authorize(
args: AuthorizePayload,
): boolean | Promise<boolean>;
}
공통적으로 사용되는 role validate 로직을 validateRole method로 구현하여 다른 여러 라우터나 컨트롤러에서 동일한 권한 검사를 필요로 할 때 중복된 코드를 작성하지 않아도 됩니다. 이를 통해 코드의 재사용성을 높일 수 있습니다.
AbstractPermissionGuard를 추상화하여, 필요에 따라 자식 클래스에서 구체적인 로직을 구현하기 때문에 향후 변경이나 추가 요구사항이 있을 때도 해당 로직만 수정하면 되므로 유지보수가 용이해집니다.
AbstractPermissionGuard를 상속하여 구현된 커스텀 가드는 기존 로직을 기반으로 새로운 권한 검사를 추가하거나 수정할 수 있습니다. 새로운 권한 체계나 권한 그룹을 추가할 때 AbstractPermissionGuard의 기능을 확장하여 해당 권한 검사를 수행할 수 있습니다.
⭐️ 위의 예시와 같이 구현하여, 지정된 role이 아닌 사용자가 특정 API에 접근 할 경우, guard에서 forbidden을 띄우고 접근을 차단하여 role을 기반으로 API 접근을 차단 기능을 구현하였습니다.