Nest.js의 Guard는 요청을 보호하고 특정 조건을 충족하는지 검사하기 위한 기능입니다.
주로 인증, 권한 부여 등의 보안 관련 로직을 처리하는 데 사용됩니다.
Guard는 요청이 컨트롤러나 라우트 핸들러에 도달하기 전에 실행되며, 요청을 통과시킬지 여부를 결정합니다.
Nest.js에서 AuthGuard는 주로 사용자 인증을 처리하는 데 사용됩니다.
AuthGuard는 CanActivate 인터페이스를 구현해야 하며, true를 반환하면 요청을 허용하고, false를 반환하면 요청을 차단합니다.
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
import { Reflector } from '@nestjs/core';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest();
const user = request.user;
// 로그인 여부 검사 (가령 JWT 또는 세션 인증)
return !!user;
}
}
컨트롤러에 @UseGuards 데코레이터를 적용하여 AuthGuard를 사용할 수 있습니다.
import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from './auth.guard';
@Controller('profile')
export class ProfileController {
@Get()
@UseGuards(AuthGuard)
getProfile() {
return "This is a protected route.";
}
}
Nest.js에서 데코레이터는 메타데이터를 사용해 특정 로직을 추가하거나 사용자 정의 데이터를 처리할 수 있습니다.
예를 들어, 요청의 특정 값을 추출하는 데 유용합니다.
여기서는 @User 데코레이터를 만들어 요청 객체에서 user 정보를 가져오도록 하겠습니다.
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const User = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user;
},
);
컨트롤러에서 이 데코레이터를 사용하면, 자동으로 요청에서 user 정보를 추출할 수 있습니다.
import { Controller, Get } from '@nestjs/common';
import { User } from './user.decorator';
@Controller('profile')
export class ProfileController {
@Get()
getProfile(@User() user: any) {
return user;
}
}
Bearer 토큰을 검증할 때, 토큰 유효성을 체크하고, 유효한지 여부에 따라 사용자의 인증 상태를 결정할 수 있습니다.
이 과정에서 보통 jsonwebtoken 라이브러리를 활용해 JWT 토큰을 검증합니다.
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import * as jwt from 'jsonwebtoken';
@Injectable()
export class AuthMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
const authHeader = req.headers['authorization'];
if (authHeader) {
const token = authHeader.split(' ')[1]; // Bearer Token 분리
if (token) {
try {
// JWT 검증
const decoded = jwt.verify(token, 'your-secret-key');
req.user = decoded;
} catch (err) {
// 검증 실패 시 처리
return res.status(401).json({ message: 'Invalid token' });
}
}
}
next();
}
}
Middleware는 app.use를 통해 전역으로 등록하거나, 특정 경로에만 적용할 수 있습니다.
import { Module, MiddlewareConsumer, RequestMethod } from '@nestjs/common';
import { AuthMiddleware } from './auth.middleware';
import { ProfileController } from './profile.controller';
@Module({
controllers: [ProfileController],
})
export class AppModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(AuthMiddleware)
.forRoutes({ path: 'profile', method: RequestMethod.GET });
}
}
Bearer 토큰 검증을 개선하려면, 토큰의 만료 시간을 체크하거나, 리프레시 토큰 전략을 추가할 수 있습니다.
예를 들어
만료 시간 체크: jwt.verify 함수는 토큰이 만료되면 TokenExpiredError를 던집니다.
리프레시 토큰 처리: 토큰이 만료되면 클라이언트에게 리프레시 토큰을 발급하는 전략을 사용합니다.
try {
const decoded = jwt.verify(token, 'your-secret-key');
req.user = decoded;
} catch (err) {
if (err.name === 'TokenExpiredError') {
return res.status(401).json({ message: 'Token expired' });
}
return res.status(401).json({ message: 'Invalid token' });
}
이러한 방식을 통해 AuthGuard, 커스텀 데코레이터, 그리고 Bearer 토큰 검증을 안전하고 효율적으로 구현하여 사이드 프로젝트에 적용하는 기회가 되었습니다.