[NestJS] Websocket을 활용한 실시간 채팅 구현 #3 - jwt
간단하게 socket.io를 통해 브라우저에서 채팅이 되는 것을 확인했다.
이제 채팅 프로그램처럼 보강을 해보고자 한다.
우선 JWT 토큰을 활용하여 로그인 / 회원가입 기능을 구현해보려고 한다.
순서
1. 환경 설정 / 필요 모듈 설치
2. JwtModule 설정
3. JwtStrategy 구현
4. 로그인 및 토큰 발행 로직 구현
5. 인증 컨트롤러 구현
6. Jwt 가드 구현
패키지 설치 / 프로젝트 구조 설정
JWT 관련 필요 패키지 설치
npm install @nestjs/jwt @nestjs/passport passport passport-jwt
npm install @types/passport-jwt --save-dev
jwt 토큰을 생성하는데 passport를 활용하여 기능을 구현하기 위해 passport-jwt 패키지를 설치해주었다.
Auth 모듈, 서비스, 컨트롤러 생성
nest g module auth
nest g service auth
nest g controller auth
User 모듈, 서비스, 컨트롤러 생성
nest g module users
nest g service users
nest g controller users
프로젝트 구조마다 다르겠지만 로그인 / 회원가입 관련된 비즈니스 로직은 auth 모듈에 구현하고
이외의 것들은 User 모듈에 구현하려고 구분했다.
인증과 관련된 내용이므로 jwt 관련된 사항도 auth 모듈에 구현해보고자 한다.
현재 프로젝트 구조는 아래와 같다
Jwt 모듈 설정
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { JwtModule } from '@nestjs/jwt';
@Module({
imports: [
JwtModule.register({
secret: process.env.JWT_SECRET,
signOptions: { expiresIn: '60s' },
}),
],
providers: [AuthService],
controllers: [AuthController],
})
export class AuthModule {}
이렇게 auth.module.ts에 JwtModule을 import하여 환경변수, 만료 기한과 같은 최초 설정을 해주었다.
모듈 설정에서 모듈을 초기화하는 방식에서 주요 차이점이 있는 메서드가 있다.
- JwtModule.register() : 동기적으로 JWT 관련 설정을 초기화
- JwtModule.registerAsnyc() : 비동기적으로 JWT 관련 설정을 초기화, ConfigModule과 통합이 필요한 경우 적합하다.
JwtStrategy 구현
jwt를 검증하는 방법을 정의해보자.
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private readonly authService: AuthService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: process.env.JWT_SECRET,
});
}
async validate(payload: any) {
return { userId: payload.sub, username: payload.username };
}
}
passport 전략을 사용해서 jwt를 인증할텐데, passport의 strategy 클래스를 상속받아 전략을 정의할 수 있다.
생성자 내부에 호출에 필요한 옵션 객체를 전달했는데, 헤더에서 'Bearer' 토큰 형식을 사용하여 jwt 를 추출하고, 토큰이 만료되었다면 요청이 거부되도록 설정했다.
로그인 및 토큰 발행 로직 구현
회원가입과 동시에 로그인이 되도록 구현하고자하니 회원가입과 로그인 과정에서 토큰 생성 관련 로직이 중복되어
별도의 service.ts를 생성해서 관리해주었다.
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class JwtTokenService {
constructor(private jwtService: JwtService) {
console.log('JwtService secret:', this.jwtService['options']);
}
generateToken(username: string, userid: string): string {
const payload = { username, sub: userid };
console.log('Using JWT Secret:', process.env.JWT_SECRET);
const token = this.jwtService.sign(payload);
console.log('Token generated:', token); // 생성된 토큰 출력
return token;
}
}
위 서비스를 auth.service.ts에서 호출해서 회원가입과 로그인 과정에서 토큰을 생성하고 반환하도록 했다.
signup과 login 의 service 로직은 생략...
인증 컨트롤러 구현
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Post('signup')
async signUp(@Body() createUserDto: CreateUserDto) {
return this.authService.signUp(createUserDto);
}
@HttpCode(HttpStatus.OK)
@Post('login')
async login(@Body() loginUserDto: LoginUserDto) {
return this.authService.login(loginUserDto);
}
}
Jwt 가드 구현
jwt를 사용하여 API 요청을 보호하기 위해 'JwtAuthGuard'를 구현했다.
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}
@UseGuards(JwtAuthGuard) 데코레이터를 사용하면 해당 API 엔드포인트는 가드에 의해 보호받는다.
Jwt토큰을 기반으로 요청을 인증하는 역할을 한다고 이해하면 된다.
아직까진 잘된다.. 휴....