JWT란?
JWT (JSON Web Token)는 당사자 간에 정보를 JSON 개체로 안전하게 전송하기위한 컴팩트하고 독립적인 방식을 정의하는 개방형 표준(RFC 7519)이다. 이 정보는 디지털 서명이 되어 있으므로 확인하고 신뢰할 수 있다.
- 간단하게 얘기하자면 정보를 안전하게 전할 때 혹은 유저의 권한 같은 것을 체크를 하기 위해서 사용하는데 유용한 모듈이다.
Passport 모듈과 함께 JWT 인증 처리해보기
npm install @nestjs/jwt @nestjs/passport passport passport-jwt --save
JWT 토큰 생성받기
모듈에 JWT, Passport 등록해주기
// auth.module.ts
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { User } from './user.entity';
import { UserRepository } from './user.repository';
@Module({
imports: [
PassportModule.register({
defaultStrategy: 'jwt',
}),
JwtModule.register({
secret: 'Secret1234',
signOptions: {
expiresIn: 3600,
},
}),
TypeOrmModule.forFeature([UserRepository]),
],
controllers: [AuthController],
providers: [AuthService, UserRepository],
exports: [AuthService],
})
export class AuthModule {}
service에서 토큰 생성하기
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { AuthCredentialDto } from './dto/auth-credential.dto';
import { User } from './user.entity';
import { UserRepository } from './user.repository';
import * as bcrypt from 'bcryptjs';
import { UnauthorizedException } from '@nestjs/common/exceptions';
import { JwtService } from '@nestjs/jwt/dist';
@Injectable()
export class AuthService {
constructor(
@InjectRepository(UserRepository)
private userRepository: UserRepository,
private jwtService: JwtService,
) {}
async signUp(authCredentialDto: AuthCredentialDto): Promise<void> {
return this.userRepository.createUser(authCredentialDto);
}
async signIn(
authCredentialDto: AuthCredentialDto,
): Promise<{ accessToken: string }> {
const { username, password } = authCredentialDto;
const user = await this.userRepository.findOneBy({ username });
if (user && (await bcrypt.compare(password, user.password))) {
// 유저 토큰 생성(secret + payload)
const payload = { username };
const accessToken = await this.jwtService.sign(payload);
return { accessToken: accessToken };
} else {
throw new UnauthorizedException('login failed');
}
}
}
jwtService의 sign()을 통해 로그인 시 토큰을 생성 받을 수 있다.
controller 작성
import { Controller, Post, Body, ValidationPipe } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthCredentialDto } from './dto/auth-credential.dto';
@Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
@Post('/signup')
signUp(
@Body(ValidationPipe) authCredentialDto: AuthCredentialDto,
): Promise<void> {
return this.authService.signUp(authCredentialDto);
}
@Post('/signin')
signIn(
@Body(ValidationPipe) authCredentialDto: AuthCredentialDto,
): Promise<{ accessToken: string }> {
return this.authService.signIn(authCredentialDto);
}
}
리턴 받는 객체 타입은 { accessToken : token값 } 형식으로 들어오게 되므로 타입을 맞춰 지정해준다.
Passport를 해서 요청 안에서 유저 정보 얻기.
유저가 요청을 보낼 때 그 요청 안에 있는 Header에 토큰을 넣어서 요청을 보내게 된다. 그것을 통해서 요청 안에 유저 정보가 들어 있게 해줄 수 있다. 그 유저 정보를 통해서 인증 처리 혹은 권한 처리 같은 것을 해줄 수 있는데, 이러한 처리를 쉽게 해주는 것이 Passport 모듈이다.
jwt.strategy.ts 작성
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { InjectRepository } from '@nestjs/typeorm';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { User } from './user.entity';
import { UserRepository } from './user.repository';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
@InjectRepository(UserRepository)
private userRepository: UserRepository,
) {
super({
// 토큰이 유효한지 확인하기 위한 키
secretOrKey: 'Secret1234',
// 클라이언트에서 오는 토큰이 어디에서 오는지 명시 해 줌
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
});
}
async validate(payload: { username: string }) {
const { username } = payload;
const user: User = await this.userRepository.findOneBy({ username });
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
controller에 인증 관련 테스트 코드 작성
import {
Controller,
Post,
Body,
ValidationPipe,
Req,
UseGuards,
} from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { AuthService } from './auth.service';
import { AuthCredentialDto } from './dto/auth-credential.dto';
@Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
// 중략
@Post('/test')
@UseGuards(AuthGuard())
test(@Req() req) {
console.log('req', req);
}
}
위와 같이 @UseGuards(AuthGuard()) 데코레이터(허용된 유저가 아니면 요청 자체를 막는 데코레이터)를 넣어 준 후 토큰 값(로그인 요청 후 받은 토큰 값)과 함께 api 요청을 보내게 되면
유저 정보가 담긴 객체가 반환이 된다.
만약 존재하지 않는 토큰 값을 포함하여 요청을 보내게 된다면
{
"statusCode": 401,
"message": "Unauthorized"
}
이와 같은 오류 메세지를 반환 해 준다.
결론적으론 요청 후 req.user로 접근을 하게 된다면 해당 토큰 유저의 정보에 대해 접근이 가능하다.
그렇다면 req.user가 아닌 바로 user라는 파라미터로 가져올 수 있는 방법은? - 커스텀 데코레이터 사용하기
get-user.decorator.ts 생성 후 작성하기
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { User } from './user.entity';
export const GetUser = createParamDecorator(
(data, ctx: ExecutionContext): User => {
const req = ctx.switchToHttp().getRequest();
return req.user;
},
);
controller 수정
import {
Controller,
Post,
Body,
ValidationPipe,
Req,
UseGuards,
} from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { AuthService } from './auth.service';
import { AuthCredentialDto } from './dto/auth-credential.dto';
@Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
// 중략
@Post('/test')
@UseGuards(AuthGuard())
test(@GetUser() user: User) {
console.log('user', user);
}
}
위와 같이 커스텀 데코레이터를 사용해서 요청을 보내게 된다면
user User {
id: 10,
username: 'GHGHgh',
password: '$2a$10$JQYn2m5zYOaybzYUMw8BteEb3TWEkFYILU8sPyw2JtPhLS37jZnPm'
}
이와 같이 user 객체만 따로 반환 시켜 가져올 수 있게 된다.
'Nest.js' 카테고리의 다른 글
[Nest.js] 본인의 게시물만 보고 쓰고 삭제하기 (0) | 2023.01.30 |
---|---|
[Nest.js] 인증된 유저만 게시물 보고 쓸 수 있게 만들기 (0) | 2023.01.30 |
[Nest.js] 회원가입과 로그인 구현 2 (0) | 2023.01.27 |
[Nest.js] 회원가입과 로그인 구현 1 (0) | 2023.01.27 |
[Nest.js] Nest.js + PostgreSQL + typeORM CRUD 구현하기 3 (0) | 2023.01.26 |