낙서장이자 오답 노트이자 컨닝 페이퍼
패스트캠퍼스 환급챌린지 27일차 : 코드팩토리의 백엔드 아카데미 : 한 번에 끝내는 NestJS 패키지 - 기초부터 MSA까지 강의 후기 본문
패스트캠퍼스 환급챌린지 27일차 : 코드팩토리의 백엔드 아카데미 : 한 번에 끝내는 NestJS 패키지 - 기초부터 MSA까지 강의 후기
NangIn 2025. 4. 27. 23:11본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성하였습니다.
강의 내용 정리
로그인
로그인 엔드포인트 구성
-
@Post('login') loginUser(@Headers('Authorization') token: string) { return this.authService.login(token); }- 컨트롤러에서 @Post('login')으로 로그인 라우트 설정
- @Headers('Authorization')을 통해 Basic Token 수신
- 서비스 메서드 login(token: string)에 위임
Basic Token 디코딩
- parseBasicToken() 유틸로 다음 작업 수행:
- 'Basic base64Encoded' → 공백 기준 split
- base64Encoded를 디코딩하여 "email:password" 포맷 획득
- : 기준 split → 이메일과 비밀번호 분리
- 유효하지 않으면 BadRequestException 발생
사용자 확인 및 비밀번호 비교
- 이메일로 사용자 조회:
-
const { email, password } = this.parseBasicToken(rawToken); const user = await this.userRepository.findOne({ where: { email } }); if (!user) throw new BadRequestException('잘못된 로그인 정보입니다!');- 없으면 → "잘못된 로그인 정보입니다." 예외
-
- 비밀번호 비교:
-
const passOk = await bcrypt.compare(password, user.password); if (!passOk) throw new BadRequestException('잘못된 로그인 정보입니다!');- 실패 시 동일 예외 던짐 (힌트 방지 목적)
-
JWT 모듈 설정 및 토큰 발급
- @nestjs/jwt 설치
-
pnpm i @nestjs/jwt
-
- JwtModule.register()로 모듈 구성
-
// auth.module.ts @Module({ imports: [TypeOrmModule.forFeature([User]), JwtModule.register({})], controllers: [AuthController], providers: [AuthService], })
-
- 환경변수에 비밀 키 및 만료시간 설정
-
#JWT ACCESS_TOKEN_SECRET=codefactory REFRESH_TOKEN_SECRET=codefactory
-
- app.moule.ts에 등록
-
ConfigModule.forRoot({ isGlobal: true, validationSchema: Joi.object({ //... ACCESS_TOKEN_SECRET: Joi.string().required(), REFRESH_TOKEN_SECRET: Joi.string().required(), }), }),
-
- jwtService 주입
-
constructor( // ... private readonly jwtService: JwtService, ) {}
-
- 토큰 두 개 발급:
-
const accessTokenSecret = this.configService.get<string>( 'ACCESS_TOKEN_SECRET', ); const refreshTokenSecret = this.configService.get<string>( 'REFRESH_TOKEN_SECRET', ); return { refreshToken: await this.jwtService.signAsync( { sub: user.id, role: user.role, type: 'refresh', }, { secret: refreshTokenSecret, expiresIn: '24h', }, ), accessToken: await this.jwtService.signAsync( { sub: user.id, role: user.role, type: 'access', }, { secret: accessTokenSecret, expiresIn: 300 }, ), }; - Access Token
- 유효기간: 5분 (300s)
- 용도: 프라이빗 API 접근
- Refresh Token
- 유효기간: 24시간
- 용도: Access Token 재발급
- JWT Payload 구성
- Access/Refresh 공통 페이로드:
-
{ sub: user.id, role: user.role, type: 'access' | 'refresh' } - signAsync() 호출 시 옵션 별도 적용
-
학습 후기
이번 강의를 통해 로그인 로직을 직접 구현하며 인증 시스템의 신뢰성과 보안성을 어떻게 확보하는지 구체적으로 이해할 수 있었습니다. Basic Auth를 이용한 로그인 요청 수신부터 JWT 토큰 발급까지의 흐름을 손으로 따라가면서, 인증이 "단순히 동작하는 것"과 "보안적으로 안전한 것"은 완전히 다르다는 사실을 체감했습니다.
Basic 토큰을 디코딩하고 이메일과 비밀번호를 분리하는 과정은 간단해 보였지만, 입력 포맷 오류에 대한 철저한 검증이 중요하다는 점을 깨달았습니다. 입력값 하나조차 방어적으로 다루지 않으면 보안 취약점으로 이어질 수 있다는 교훈이 인상 깊었습니다.
bcrypt를 이용한 비밀번호 비교에서는, 저장된 해시값과 입력값을 안전하게 비교하는 것이 얼마나 중요한지 느꼈습니다. 또한 로그인 실패 시 항상 동일한 에러 메시지를 반환해 해커에게 단서를 주지 않는 세심한 설계가 필요하다는 것도 알게 되었습니다.
JWT 발급 과정에서는 access token과 refresh token을 분리하고, 각각 다른 secret과 만료 시간을 설정해 보안과 UX를 모두 고려하는 방법을 배웠습니다. 특히 jwt.io를 통해 직접 디코딩해보면서, Header, Payload, Signature 구조와 변조 검증 방식까지 확실히 이해할 수 있었습니다.
이번 과정을 통해 인증 시스템을 구축할 때 단순 구현이 아닌 보안을 항상 우선순위에 두어야 함을 다시 느꼈습니다. 앞으로도 인증 로직을 구현할 때 이번 경험을 바탕으로 더욱 신중하고 견고하게 설계하겠다는 다짐을 하게 되었습니다.
학습 인증샷



