전체 코드 : https://github.com/jeounpar/nestjs-jwt-roleguard-tutorial
JWT란 무엇인가요?
1. nest 프로젝트 생성 및 auth 모듈 생성
$ nest new jwt-tutorial
$ cd jwt-tutorial
$ nest generate module auth
$ nest generate service auth
$ nest generate controller auth
2. JWT 및 환경변수 라이브러리 설치, .env 파일 생성
$ npm install @nestjs/jwt @nestjs/config
// .env
JWT_SECRET="여기에 시크릿 키 입력"
3. auth.guard.ts 파일 생성
// auth/auth.guard.ts
import {
CanActivate,
ExecutionContext,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { Request } from 'express';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private jwtService: JwtService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const token = this.extractTokenFromHeader(request);
if (!token) {
throw new UnauthorizedException();
}
try {
const payload = await this.jwtService.verifyAsync(token, {
secret: process.env.JWT_SECRET,
});
console.log(payload);
} catch {
throw new UnauthorizedException();
}
return true;
}
private extractTokenFromHeader(request: Request): string | undefined {
const [type, token] = request.headers.authorization?.split(' ') ?? [];
return type === 'Bearer' ? token : undefined;
}
}
CanActivate
NestJS 에서 사용되는 *가드(guard)의 일종이다. CanActivate 인터페이스는 라우터 또는 컨트롤러 메서드에 적용되며, 요청이 실행되기 전에 조건을 확인하여 요청을 허용하거나 거부하는 역할을 한다.
CanActivate 인터페이스는 단일 메서드를 가지고 있으며, 해당 메서드의 반환 값은 boolean 또는 Promise<boolean>이어야 한다. 이 메서드는 라우터 또는 컨트롤러 메서드가 실행되기 전에 호출되며, true를 반환하면 요청이 계속 진행되고, false를 반환하면 요청이 차단된다.
일반적으로 CanActivate 인터페이스를 구현하기 위해 사용자 정의 가드 클래스를 작성하게 되는데, AuthGuard 와 같은 내장된 가드 클래스를 상속하여 재사용 가능한 로직을 구현할 수도 있다.
(*가드 : 라우터 핸들러 또는 컨트롤러 메서드를 실행하기 전에 요청을 검사하고 조작하는 데 사용됨)
ExecutionContext
NestJS 에서 사용되는 실행 컨텍스트를 나타내는 인터페이스이다. 이 인터페이스는 요청을 처리하는 동안 현재 실행 중인 컨트롤러, 핸들러, 파이프, 인터셉터 등의 정보에 접근할 수 있는 메서드와 프로퍼티를 제공한다.
ex) 인터셉터에서 ExecutionContext를 이용하여 현재 실행 중인 요청의 정보에 접근하거나 조작할 수 있다.
4. auth.controller.ts 및 auth.service.ts 파일 수정
// auth.controller.ts
import { Controller, Get, Post, Req } from '@nestjs/common';
import { AuthService } from './auth.service';
@Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
@Post('login')
async login(@Req() req) {
const id: string = req.body.id;
const password: string = req.body.password;
return await this.authService.login(id, password);
}
@Get('verify')
async verify(@Req() req) {
const [type, token] = req.headers.authorization?.split(' ') ?? [];
return await this.authService.verify(token);
}
}
// auth.service.ts
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class AuthService {
constructor(private jwtService: JwtService) {}
async login(id: string, password: string) {
// 프로덕션 환경에서는 페이로드에 패스워드 같은 중요 정보는 탑재 금지!!
const payload = {
id: id,
password: password,
};
const accessToken = await this.jwtService.signAsync(payload);
return accessToken;
}
async verify(token: string) {
const payload = await this.jwtService.verifyAsync(token, {
secret: process.env.JWT_SECRET,
});
return payload;
}
}
login
클라이언트로부터 id와 password를 전달받아 jwt token을 생성해준다.
verify
클라이언트로부터 jwt token을 전달받아 token에 해당하는 클라이언트의 id와 password를 알려준다.
5. app.module.ts 생성 및 auth.module.ts 파일 수정
// app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { AuthModule } from './auth/auth.module';
import { ConfigModule } from '@nestjs/config';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
}),
AuthModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
// auth.module.ts
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { JwtModule } from '@nestjs/jwt';
@Module({
imports: [
JwtModule.registerAsync({
useFactory: async () => ({
secret: process.env.JWT_SECRET,
signOptions: { expiresIn: '60s' },
}),
}),
],
providers: [AuthService],
controllers: [AuthController],
})
export class AuthModule {}
.env파일 사용을 위해 ConfigModule을 글로벌로 설정하고, auth.module 에서 @Module 안에 process.env 사용을 위해 registerAsync와 useFactory를 사용했다.
6. JWT 테스트
auth/login 테스트
auth/verify 테스트
- iat (Issued At): JWT가 발급된 시간. 이 필드는 UNIX 타임스탬프(UTC 기준)로 표현되고 보통 JWT가 발급된 시간을 추적하기 위해 사용됨.
- exp (Expiration Time): JWT의 만료 시간. 이 필드는 UNIX 타임스탬프(UTC 기준)로 표현되고 만료 시간 이후에는 JWT가 더 이상 유효하지 않음. 보통 JWT의 수명을 관리하고 재인증을 유도하기 위해 사용됨.
7. app.controller.ts 에 UseGuards 추가
// app.module.ts
...
imports: [
AuthModule,
ConfigModule.forRoot({
isGlobal: true,
}),
JwtModule.registerAsync({
useFactory: async () => ({
secret: process.env.JWT_SECRET,
signOptions: { expiresIn: '60s' },
}),
}),
],
...
// app.controller.ts
import { Controller, Get, UseGuards } from '@nestjs/common';
import { AppService } from './app.service';
import { AuthGuard } from './auth/auth.guard';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@UseGuards(AuthGuard)
@Get()
getHello(): string {
return this.appService.getHello();
}
}
8. UseGuards 테스트
'개발 > Nest.js' 카테고리의 다른 글
02) Nest.js JWT인증 + RoleGuard 가이드 (0) | 2023.06.09 |
---|---|
Nest.js serverless + AWS Lambda 배포하기 (0) | 2023.03.19 |
Nest.js interface 구현체에 DI 적용하기 (0) | 2023.03.13 |