인증(Authentication)
구글/네이버/카카오/로컬 로그인 인증 주로 사용됨
JWT를 통한 인증
어떤값을 나에게 주면 너를 OOO으로 인정할게
jwt 발급 구현
___환경설정__________
__nestjs_user폴더의 상위폴더에서 설치__________________________
nest new nestjs_user
cd nestjs_user
@nestjs/mapped-types, class-validator 패키지를 설치
npm i @nestjs/mapped-types class-validator
lodash 패키지를 설치
npm i lodash @types/lodash
________________________________________
Visual Studio Code로 게시판 프로젝트 열기
code .
__nestjs_user폴더에서 설치__________________________
npm i @nestjs/config @nestjs/jwt @nestjs/passport passport passport-jwt @types/passport-jwt typeorm @nestjs/typeorm mysql2 multer bcrypt @types/bcrypt class-validator class-transformer multer @types/multer papaparse @types/papaparse joi typeorm-naming-str @nestjs/cache-manager cache-manager
npm install mysql2 # 이게 설치가 안됬었음.. 왜죠? 위에 있는데?
user 뼈대 만들기
nest g resource user #REST API Y
tsconfig.json에 추가
"esModuleInterop": true // 추가. ES6 모듈 사양을 준수하여 CommonJS 모듈을 가져오기
prettier 설치
npm install --save-dev prettier
.prettierrc파일 추가
{
"singleQuote": true,
"trailingComma": "all",
"semi": true,
"tabWidth": 2,
"useTabs": false,
"printWidth": 80,
"bracketSpacing": true,
"jsxBracketSameLine": false,
"arrowParens": "always"
}
package.json에 추가(자동으로 추가되지만 확인해보자)
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
___________________________________________________________________________
___nestjs_board/src 폴더 안에서__________
nestjs_board프로젝트의 ./src 폴더로 이동
cd src
게시판 뼈대 한 번에 완성하기 nest g resource 폴더이름
nest g resource post # REST API, Y
결과
PS V:\Sparta\memoNote\github\D1\dailyCoding\NESTJS\3W> cd src
PS V:\Sparta\memoNote\github\D1\dailyCoding\NESTJS\3W\src> nest g resource post
? What transport layer do you use? REST API
? Would you like to generate CRUD entry points? Yes
CREATE post/post.controller.ts (917 bytes)
CREATE post/post.controller.spec.ts (576 bytes)
CREATE post/post.module.ts (250 bytes)
CREATE post/post.service.ts (633 bytes)
CREATE post/post.service.spec.ts (464 bytes)
CREATE post/dto/create-post.dto.ts (31 bytes)
CREATE post/dto/update-post.dto.ts (173 bytes)
CREATE post/entities/post.entity.ts (22 bytes)
빌드
npm run build
실행
npm run start
이렇게 뜨면 정상작동
PS V:\Sparta\memoNote\github\D1\dailyCoding\NESTJS\3W\nestjs_board\src> npm run start
> nestjs_board@0.0.1 start
> nest start
[Nest] 23560 - 2025. 01. 21. 오후 3:31:03 LOG [NestFactory] Starting Nest application...
[Nest] 23560 - 2025. 01. 21. 오후 3:31:03 LOG [InstanceLoader] AppModule dependencies initialized +6ms
[Nest] 23560 - 2025. 01. 21. 오후 3:31:03 LOG [InstanceLoader] PostModule dependencies initialized +0ms
[Nest] 23560 - 2025. 01. 21. 오후 3:31:03 LOG [RoutesResolver] AppController {/}: +3ms
[Nest] 23560 - 2025. 01. 21. 오후 3:31:03 LOG [RouterExplorer] Mapped {/, GET} route +1ms
[Nest] 23560 - 2025. 01. 21. 오후 3:31:03 LOG [RoutesResolver] PostController {/post}: +0ms
[Nest] 23560 - 2025. 01. 21. 오후 3:31:03 LOG [RouterExplorer] Mapped {/post, POST} route +1ms
[Nest] 23560 - 2025. 01. 21. 오후 3:31:03 LOG [RouterExplorer] Mapped {/post, GET} route +0ms
[Nest] 23560 - 2025. 01. 21. 오후 3:31:03 LOG [RouterExplorer] Mapped {/post/:id, GET} route +1ms
[Nest] 23560 - 2025. 01. 21. 오후 3:31:03 LOG [RouterExplorer] Mapped {/post/:id, PATCH} route +0ms
[Nest] 23560 - 2025. 01. 21. 오후 3:31:03 LOG [RouterExplorer] Mapped {/post/:id, DELETE} route +1ms
[Nest] 23560 - 2025. 01. 21. 오후 3:31:03 LOG [NestApplication] Nest application successfully started +1ms
______________________________________
__ JWT 검증 구현______________________________________
클라이언트가 헤더에 Authorization 필드로 Bearer {JWT} 를 보내면
AuthMiddleware는 JWT를 파싱하여 특정 유저임을 파악할 수 있다.
AuthMiddleware는 미들웨어로서, 직접적으로 NestJS의 DI 시스템에 포함되지 않는다.
JwtService와 같은 서비스를 사용하려면, 해당 서비스가 포함된 모듈을 AppModule에 import해야 한한다.
이 경우, JwtModule을 AppModule에 import하여 JwtService를 AuthMiddleware에서 사용할 수 있도록 했다
AppModule에 import를 하면 UserService에서도 JwtModule을 사용할 수 있다.
--test----------------
GET을 제외한 나머지 API에는 Headers에 Content-Type: application/json을 꼭 넣기
__테스트______________
_________
회원가입
req
body json
{
"userId": "test_ID",
"password": "test_PW"
}
res
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNzM3NjI4NDU2fQ.LfheKReAVfgMk8CpXzPXMG129WVoa-uS8NVUUTIxRpM
________________
로그인
req
body json
{
"userId": "test_ID",
"password": "test_PW"
}
res
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNzM3NjI4NDkxfQ.bt43YFkgzMXcPMaEqYOxPto2sIsRNStKHlg5lHh2EFM
______________
회원체크
GET http://localhost:3000/user/check
req
헤더에 로그인에서 받은 토큰 넣기
Authentication : Bearer 발급받은토큰
res
{유저 정보: {"id":1,"iat":1737628491}}
_______________________________________________
_________인가__________________________________
_______________________________________________
데코레이터
클래스나 함수와 같은 곳에 메타데이터를 추가하는 방법을 제공하는 선언 방법
코드에 추가적인 정보를 제공하여 실행 시점에 코드의 동작 방법이 제공되는 것
타데이터들로 Nest.js 프레임워크 자체가 실행 시점에 이 함수를 어떻게 호출해야 하는지 알 수 있다
컨트롤러에서 @Get데코레이터가 붙은 함수 : 함수가 HTTP GET 요청을 처리해야 함을 나타냄
커스텀 데코레이터
- 우리가 원하는 대로 데코레이터의 동작을 정의할 수 있다
- 코드에 추가적인 로직을 삽입하거나 특정 정보를 추출하거나 변경할 수 있다
인가구현시 커스텀 데코레이터
- e.g. 유저를 식별하는 데코레이터
- 요청에서 사용자 정보를 추출하고 이를 라우트 핸들러에 전달할 수 있다
- e.g2. 유저의 접근을 제어하는 데코레이터
- 사용자의 역할을 기반으로 특정 엔드포인트에 대한 접근을 제어할 수 있다
예시 - createParamDecorator
import { createParamDecorator, ExecutionContext } from "@nestjs/common";
export const UserInfo = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
if (request.user) {
return request.user;
}
return null;
}
);
createParamDecorator 함수를 이용하여 새로운 커스텀 데코레이터를 만들 수 있다.
createParamDecorator함수는 2개의 인자를 가진다.
data: 데코레이터의 인자로 전달될 수 있는 데이터
ctx: 현재 요청의 ExecutionContext(실행 컨텍스트) = Nest.js의 실행 컨텍스트
HTTP 요청과 관련된 모든 정보를 포함함
const request = ctx.switchToHttp().getRequest();
- AuthMiddleware : HTTP 요청이 라우트 핸들러에 도달하기 전에 실행된다
그 과정에서 req.user에 사용자 정보가 저장되어있다.
- 커스텀 데코레이터 UserInfo에서 ExecutionContext 타입인 ctx 변수를 이용해 현재의 HTTP 요청 객체(req)에 접근할 수 있으며 이를 통해 req.user에도 접근 할 수 있게 된다
- 즉, ctx.switchToHttp().getRequest()라는 메소드를 통해 req에 접근을 하는 것
- ctx.switchToHttp().getResponse() : 응답 객체에 접근하는 메소드 → 즉, HTTP 응답을 조작할 수 있다
커스텀 데코레이터 적용 예시
import { Controller, Get } from '@nestjs/common';
import { CurrentUser } from './current-user.decorator';
@Controller('user')
export class UserController {
@Get()
getProfile(@UserInfo() user: any) {
return user;
}
}
___________________________
__Guard____________________
Guard는 Nest.js에서 특정 라우트에 대한 접근을 제어하는 역할
ex : 인증된 사용자만 특정 라우트에 접근할 수 있도록 하거나,
관리자만 특정 기능을 사용할 수 있도록 할 때
Guard 작동 방식
- Guard는 `canActivate` 메서드를 구현하는 클래스로 구성
- `canActivate` 메서드는 라우트 접근을 허용할지 또는 거부할지 결정함.
Guard 생성 방법
nest g d 데코레이터 이름
nest g guard 데코레이터이름
Guard 코드분석
import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common';
@Injectable()
// LevelGuard라는 이름의 Guard를 생성
export class LevelGuard implements CanActivate {
// ExecutionContext : 실행컨텍스트에서 html의 request객체를 가져올 수 있다
// canActivate 메서드에서 HTTP 요청 객체를 통해 사용자 정보를 가져오기
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();// 요청 객체 가져오기
const user = request.user; // user 객체는 { level: number } 형태라고 가정합니다.
// 사용자의 레벨을 확인하여 level이 1인 경우(운영자)에만 접근을 허용하고,
// 그렇지 않은 경우 ForbiddenException을 발생시켜 접근을 거부
// level === 1 : 이넘을 쓰지는 않지만 운영자라고 취급
if (user && user.level === 1) {
return true;
}
// 접근금지
throw new ForbiddenException('접근이 금지되었습니다!');
}
}
- Guard는 Nest.js의 요소 중 하나이기 때문에 커맨드로 직접 생성할 수 있다.
Guard를 컨트롤러에 적용
import { Controller, Get, UseGuards } from '@nestjs/common';
import { LevelGuard } from './level.guard';
// admin(운영자)만 AdminController의 라우트에 접근할 수 있다
@Controller('admin')
// UseGuards를 컨트롤러 위에 세우기 = 전역적으로 적용하겠다
@UseGuards(LevelGuard)
// LevelGuard는 AdminController에 적용됨. 모든 라우트는 이제 LevelGuard에 의해 보호된다.
export class AdminController {
@Get('dashboard') // 전역적으로 사용하지 않고 함수마다 사용할 수 있음
getDashboard() {
return { message: 'Admin Dashboard' };
}
}
커스텀 데코레이터와 Guard를 활용하면 인가 기능을 구현할 수 있다(6주차 더 참고하기)
NestJS에서 IoC, DI, AOP는 애플리케이션 구조와 설계의 핵심적인 개념으로, 유지보수성과 확장성을 높이는 데 중요한 역할을 함
IoC (Inversion of Control): 객체 생성과 관리의 제어권을 프레임워크에 위임.
DI (Dependency Injection): 필요한 의존성을 외부에서 주입받아 사용. @Injectable() 데코레이터를 사용
AOP (Aspect-Oriented Programming): 핵심 로직과 부가적인 관심사를 분리하여 깔끔한 코드 구조 유지.
AOP
여러 부분에 걸쳐서 반복되는 공통 관심사를 분리하고 중앙에서 관리할 수 있게 하는 프로그래밍 기법
코드의 모듈성을 향상시키고 중복을 줄인다.
JavaScript에서는 프록시 객체나 헬퍼 객체들로 이러한 개념을 구현할 수 있다.
프록시 객체는 JavaScript 내장 객체
다른 객체의 기본 동작을 사용자 정의 동작으로 수정할 수 있게 함
로깅이나 인증 및 에러 처리 등
헬퍼 객체는 특정 작업을 수행하는 메서드만 입맞에 맞게 정의를 하고 이를 통해 코드의 재사용성을 높일 수 있다
공통 기능을 모듈화하고 이를 여러 부분에서 호출
AOP 개념을 구현하기
인터셉터(가로체다) : AOP 개념을 구현하는 핵심 요소. 특정 작업을 수행하기 전이나 후에 추가 로직을 실행할 수 있는 코드 블록
- HTTP 요청과 응답을 처리할 때 특히 유용.
- 주로 로깅, 에러 처리, 데이터 변환 및 인증과 같은 공통 관심사를 처리하는 데 사용
인터셉터 구현
어떠한 요소를 만들기 전에는 보통
1) Nest.js에서 제공하는 인터페이스를 구현하거나
2) 명령어를 통해 요소를 만드는 절차가 꼭 있다
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
@Injectable()// 의존성 주입
// 인터셉터를 만들기 위해서는 NestInterceptor 인터페이스를 구현하는 것이 필수
export class LoggingInterceptor implements NestInterceptor {
// intercept라는 메소드를 통해 요청을 가로챈 후 next.handle()을 호출
// = 요청을 처리하고 응답을 반환하는 컨트롤러 메소드로 이동을 한다는 뜻
// 즉, 요청이 처리된 이후임을 뜻
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const now = Date.now();
return next
.handle() // next.handle()
// pipe()라는 함수로 요청 처리 파이프라인의 다음단계로 이동을 한 후
// tap()이라는 연산자 내에서 응답이 처리된 시간을 로그로 찍는다.
.pipe(tap(() => console.log(`완료에 걸린 시간: ${Date.now() - now}ms`)));
}
}
_____________________________________________
_____캐싱_____________________________________
자주 변하지 않는 데이터에 동일한 요청이 지속적으로 들어오는 경우 저장해드고 필여할 때때
Nest.js에서는 `cache-manager`와 연계를 하여 캐싱 기능을 사용할 수 있다
npm i @nestjs/cache-manager cache-manager
적용하기
app.module.ts
post.service.ts
______________________________________________