현 서비스에는 권한이 계층형입니다.
① 전역: 비회원 ↔ 회원(JWT)
② 커뮤니티: 여러 커뮤니티 중 “요청된 커뮤니티에 가입된 멤버인지”
③ 역할: 그 커뮤니티에서의 일반 유저 / 아티스트 / 매니저
컨트롤러에서 매번 if–else로 검사하면 중복·누락이 생기고, 정책이 코드 곳곳으로 흩어집니다.
그래서 권한은 선언, 검증은 가드에서 처리하도록 변경했습니다.
문제
개선 방안
@UseGuards(JwtAuthGuard, CommunityUserGuard) 표준화@CommunityUserRoles(...) 로 허용 role 선언Controller (선언)
└─ @CommunityUserRoles(…optional) + @UserInfo() // 권한 역할 선언 + 유저 주입
↓
JwtAuthGuard (① 전역: 로그인/회원)
↓
CommunityUserGuard
├─ ② 커뮤니티 멤버인지 검사
└─ ③ (필요시) 커뮤니티 역할(ARTIST/MANAGER) 검사
아래 코드는 흐름을 설명하기 위해 실제 구현한 코드를 간소화한 버전입니다.
// community-user-role.type.ts
export enum CommunityUserRole {
ARTIST = 'ARTIST',
MANAGER = 'MANAGER',
// 필요시 다른 역할 확장
}
// community-user-roles.decorator.ts
import { SetMetadata } from '@nestjs/common';
import { CommunityUserRole } from './community-user-role.type';
export const COMMUNITY_ROLES_KEY = 'communityRoles';
export const CommunityUserRoles = (...roles: CommunityUserRole[]) =>
SetMetadata(COMMUNITY_ROLES_KEY, roles);
위 Enum만 CommunityUser의 허용 role로 선언합니다.
@CommunityUserRoles() 내부에 들어간 값을 읽어 가드에서 실제 검증에 사용합니다.
UserInfo()로 유저 정보 받기// user-info.decorator.ts
import { ExecutionContext, createParamDecorator } from '@nestjs/common';
export const UserInfo = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user;
},
);
매번 req.user 를 사용해 직접 꺼내오지 않고 @UserInfo 데코레이터를 만들어 유저 정보를 가져올 수 있도록 했습니다.
Before/After
// Before
@Post()
create(@Req() req: Request, @Body() dto: CreateDto) {
const user = req.user; // 매번 이렇게 꺼냄
...
}
// After
@Post()
create(@UserInfo() user: PartialUser, @Body() dto: CreateDto) {
...
}
// community-user.guard.ts
@Injectable()
export class CommunityUserGuard implements CanActivate {
constructor(
private readonly reflector: Reflector,
// ...
) {}
async canActivate(ctx: ExecutionContext): Promise<boolean> {
const req = ctx.switchToHttp().getRequest();
// 1) ① 로그인 여부 확인
const userId = req.user?.id;
if (!userId) throw new UnauthorizedException();
// ...
// 2) ② 해당 커뮤니티 유저인지 검사
const existedCommunityUser =
await this.communityUserService.findByCommunityIdAndUserId(
communityId,
userId,
);
// ...
// 3) @CommunityUserRoles 데코레이터 안 roles를 가져오는 역할
// ex) @CommunityUserRoles(ARTIST,MANAGER) → roles [ARTIST,MANAGER]
const roles: CommunityUserRole[] = this.reflector.get(
context.getHandler(),
);
// 4) roles 데코레이터 없으면 : 멤버면 통과
if (roles.length === 0) return true;
// 5) ③ 역할 검사(지정된 roles 중 맞으면 통과)
const communityUserid = existedCommunityUser.communityUserId;
// ③ 커뮤니티 유저이면서 ARTIST roles를 가진 경우
if (roles.includes(CommunityUserRole.ARTIST)) {
// ...
}
// ③ 커뮤니티 유저이면서 MANAGER roles를 가진 경우
if (roles.includes(CommunityUserRole.MANAGER)) {
// ...
}
// ...
throw new ForbiddenException('존재하지 않는 역할입니다.');
}
}
// 1) 해당 커뮤니티 “멤버면” 접근
@UseGuards(JwtAuthGuard, CommunityUserGuard)
@Post(':communityId/assets')
createAsset(
@UserInfo() user: PartialUser,
@Body() dto: CreateAssetDto,
) { /* ... */ }
// 2) “아티스트 또는 매니저”만 접근
@CommunityUserRoles(CommunityUserRole.ARTIST, CommunityUserRole.MANAGER)
@UseGuards(JwtAuthGuard, CommunityUserGuard)
@Post(':communityId/publish')
publish(
@UserInfo() user: PartialUser,
@Body() dto: PublishDto,
) { /* ... */ }
// 3) “매니저만” 접근
@CommunityUserRoles(CommunityUserRole.MANAGER)
@UseGuards(JwtAuthGuard, CommunityUserGuard)
@Delete(':communityId/posts/:postId')
removePost(
@Param('postId') postId: string,
@UserInfo() user: PartialUser,
) { /* ... */ }
기존 서비스 로직에 있던 코드들을 Guard를 사용하는 식으로 변경하고 나니 개발한 사람 외에는 팀원들이 사용할 수도 없을거란 생각을 했고, 쉽게 이해/사용할 수 있도록 가이드문서가 필요하다는 생각을 했습니다. 위 내용과 많이 중복되기는 하지만, 혹시 가이드 문서가 필요하다고 생각하신 분들이 참고할 수 있도록 짧게 사진 넣습니다 🥹
코드의 대부분은 NestJS 공식 문서에 관련 내용이 적혀있습니다!
Guards | NestJS - A progressive Node.js framework
Custom decorators | NestJS - A progressive Node.js framework