[Nest.js][TIL] 타임어택 해커톤 - 로그인 / 회원가입

Trippy·2024년 1월 8일
0

Nest.js

목록 보기
11/15
post-thumbnail

개발 공부한지 어연 4개월...

공부 시작한지도 4개월이 훌쩍 넘었고..첫 프로젝트로 했었던 간단한 게시판 crud를 만들면서 와.. 이걸 나중에 혼자서 만들 수 있을까? 하는 생각들은 절대 가능 하지 않을 거란 생각을 가졌었다.
하지만 결국 비슷한 crud를 수없이 많이 만들어보고 그 중 특히 모든 서비스에서 빠지지 않는 로그인과 관련된 인증, 인가와 같은 것들은 이제 몸에 익숙해졌다.

타임어택 과제 구성

E-Commerce의 기본 제품 요소인 로그인, 회원가입 기능을 바닥부터 만들어 주는 것이다.
이는 E-Commerce형태를 띠는 거의 모든 제품의 기본적인 기능으로, 개발자로서 기본을 탄탄히 만들어가는데 매우 도움이 된다고 생각한다.

개발 역량에서 중요하다고 생각하는 것.

  1. 모르는 것을 떠올리는 사고력
  2. 생각을 구체적으로 만들어낼 수 있는 구현력
  3. 다른 사람과 협업할 수 있는 커뮤니케이션 스킬

나는 여기서 특히 강점은 생각을 구체적으로 만들어낼 수 있는 구현력 이었던 것 같다.
항상 상상을 많이 하는 성격 덕분에 상상력이 뛰어나서 상상한대로 한글로 손코딩 하고, 코드 구현에 돌입하면 결국 내가 생각했던 상상을 바탕으로 코드가 완성되고, 그 코드가 실제로 작동이 결국엔 된다.

그럴때 마다 신기했지만 또 한가지 부족한 점이라고 한다면. 말 그대로 "구현" 하는데에만 급급해 성능적으로나 코드 리팩토링적 측면에서 바라봤을때 코드가 가독성이 좋지도 않고, 구조 아키텍처에 근거해서 잘 나눠져서 모듈화가 이루어져 있지도 않고, 코드들이 일관성 있게 모여있지 않아서 나중에 디버깅할 때 정말 어려워 질 수 있는 반쪽짜리구나 라는 내 약점을 항상 느낄 수 있었다.

그 점들을 해결하기 위하여 기본적으로 내가 사용하고 있는 도구들인 언어와 프레임워크 SQL과 같은 기술적인 부분에서는 충분히 기본기를 다지고 사용법을 잘 숙지해야지 내가 그때 그때 필요하다고 느끼는 부분에서 적재적소에 알맞게 잘 사용할 수 있을 것이라고 생각한다.

프로젝트 과정

1) 세팅

  1. nest.js 세팅 및 필요한 리소스들을 제네레이터 해 준다.
  2. TypeORM 을 db로 사용하기 위해서 서버와 연결을 한다.
  3. 환경변수: jwt시크릿 코드나 db 연결코드들을 등록해주기 위해서 .env에 저장하고 .gitignore에 등록해준다.
// app.module.ts

import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from '@nestjs/config';
import { ConfigModuleValidationSchema } from 'configs/env.valid';
import { TypeOrmModule } from '@nestjs/typeorm';
import { typeOrmModuleOptions } from 'configs/database.config';
import { UserModule } from './user/user.module';
import { AuthModule } from './auth/auth.module';
import { SecurityMiddleware } from './middleware/security.middleware';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      validationSchema: ConfigModuleValidationSchema,
    }),
    TypeOrmModule.forRootAsync(typeOrmModuleOptions),
    UserModule,
    AuthModule,
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(SecurityMiddleware).forRoutes('*');
  }
}

2) 구현을 위한 파일 구조를 미리 만들어준다.


내가 이번에 만들 것들은 8시간 안에 로그인과 회원가입을 완벽하게 만드는 것이다.

시간이 부족하다면 기본 기능만 구현하고 시간이 낮으면 회원crud를 모두 다 할 예정이다.

회원가입을 먼저 구현하기 위해서 User의 엔티티를 먼저 만들어준다.
또한 만들어 주면서 미리 swagger를 작성할 수 있는 데코레이터를 사용한다.

3) 회원가입과 로그인 controller, service를 구현한다

기본적인 crud하듯 body로 만든 값을 db에 저장해주는 코드는 금방 짜게 되었다.

여기서 초보자들은 햇갈리거나 깜빡할 수 있는데 자기가 사용할 repository가 있다면
꼭 데코레이터로 엔티티를 호출하면서 레포지를 연결 한 다음, 각 기능에 맞는 module에서 엔티티를 추가시켜 줘야한다.

db연결이 안되서 안되면 순간적으로 뇌 정지 와서 짜증날 수 있음.

4) jwt

이번 프로젝트의 가장 꽃은 사실 jwt이다.

jwt는 실제 서비스에서 로그인 인증,인가에서 가장 중요한 역할을 하는 아주 효자 녀석이다.
이 친구를 이용해서 accessToken과 refreshToken을 사용하여 클라이언트가 로그인 한 상태임을 서버가 알 수 있도록 해줄 수 있고 accessToken의 유효기간이 지나서 로그아웃되는 불편함을 없애기 위해서 리프래쉬토큰이 새로 생기고.. 어쨋든 이런 인증, 인가에 관련된 부분에선 모두가 인정할 만큼 중요한 부분이다.

jwt를 가드로 사용하기 위해서 전략파일을 만들어주었다

// src/starategies/jwt.strategy.ts

import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { JwtPayload } from '../interfaces/jwt-payload.interface';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(private configService: ConfigService) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: configService.get<string>('JWT_ACCESS_SECRET'),
    });
  }

  validate(payload: JwtPayload) {
    return payload;
  }
}

이 녀석의 역할은 리퀘스트로 받아온 bearer에 해당하는 토큰을 가져와서 등록했던 jwt 시크릿 코드로 payload를 읽을 수 있게 하는 부분이다.

그렇게 되면 payload는 로그인 성공한 클라이언트 유저가 jwt등록 할 때 넣었던 payload가 담겨져서 나올 것 이다.

여기서 엑세스 토큰과 리프래쉬 토큰에 대해서 이야기를 했을 때 나는 더이상 입 아플 정도로 많이 읽고, 또한 블로그에도 몇 번 포스팅을 한 적 있어서 역할에 대해서 나름 깊은 이해를 가지고 있다고 생각한다.

먼저 엑세스 토큰은 유저가 로그인한 상태임을 서버에게 알려주기 위해서 가지고 있는 인증키 같은 개념이다.
리프래쉬 토큰은 유저가 가지고 있는 엑세스 토큰이 탈취나 보안의 위험성 때문에 유효기간이 있는데 이 유효기간이 지나게 되면 그 토큰은 아에 사용하지 못하게 폐기가 되어버리는 것이다.

따라서 그때 그때 있을 수 있는 예외처리들을 미리 생각해두고 만들어 주는 방법도 좋았었다.

  // 엑세스토큰 재발급
  @ApiBearerAuth('access_token')
  @ApiOperation({
    summary: '액세스 토큰 갱신',
    description: '리프레시 토큰을 사용하여 새로운 액세스 토큰을 발급받습니다.',
  })
  @ApiResponse({ status: 401, description: '리프레시 토큰 유효하지 않음' })
  @Post('/refresh')
  @UseGuards(AuthGuard('jwt'))
  async refreshAccessToken(@Req() req) {
    const user = req.user;
    const currentTime = Math.floor(Date.now() / 1000);

    // if (user.exp < currentTime) {
    const refreshToken = await this.userRepository.findOne({
      where: { userId: user.userId },
      select: ['refreshToken'],
    });

    const refreshTokenVerify = await this.jwtService.verify(
      refreshToken.refreshToken,
      {
        secret: this.configService.get('JWT_REFRESH_SECRET'),
      },
    );
    console.log('래프래쉬검증', refreshTokenVerify);
    if (refreshTokenVerify.exp < currentTime) {
      throw new UnauthorizedException('재 로그인이 필요합니다.');
    }
    const newAccessToken =
      await this.authService.generateAccessToken(refreshTokenVerify);
    return {
      HttpStatus: 200,
      newAccessToken,
      // };
    };

이 코드를 작성할 당시가 마감 시간 한 시간 전이었기 때문에 service에서 만들고 나서도 최적화도 시키고 해야 하지만 시간이 너무 부족한 관계로 기능만 잘 동작하면 만족한다는 마음으로 타임어택 프로젝트를 끝을 내었다. 나는 프로젝트 하는 거 자체가 너무 재밌고 배우는 점도 많이서 오히려 시간이 부족할 지경이다..이제 한달 보다 조금 더 남은 상황에서 컨디션이 자꾸 나빠져서 공부 효율이 떨어지고 있는데 체력도 잘 안배하면서 다음날은 더 크게 많이 배우고 싶다.
profile
감금 당하고 개발만 하고 싶어요

0개의 댓글