TIL - 20260225

juni·2026년 2월 25일

TIL

목록 보기
277/316

0225 NestJS 기초 (7/N): 사용자 인증과 JWT


✅ 1. 사용자 인증(Authentication)의 기초

  • 인증이란 사용자가 "누구인지"를 확인하는 과정입니다. 웹 애플리케이션에서는 일반적으로 아이디/비밀번호를 통해 인증을 수행합니다.

  • 핵심 과제: 사용자의 비밀번호를 어떻게 안전하게 저장하고, 로그인 상태를 어떻게 유지할 것인가?

  1. 비밀번호 암호화:

    • 사용자의 비밀번호를 절대 평문으로 데이터베이스에 저장해서는 안 됩니다.
    • 해시(Hash) 함수(e.g., bcrypt)를 사용하여, 비밀번호를 복호화가 불가능한 단방향 암호화된 문자열로 변환하여 저장해야 합니다.
    • 로그인 시에는, 사용자가 입력한 비밀번호를 동일한 해시 함수로 암호화하여 DB에 저장된 해시 값과 비교합니다.
  2. 로그인 상태 유지 (JWT):

    • 인증에 성공한 사용자에게, 서버는 사용자 정보를 담은 암호화된 "인증서"JWT(JSON Web Token)를 발급합니다.
    • 클라이언트는 이 JWT를 저장해두고, 이후 인증이 필요한 모든 요청마다 이 토큰을 서버에 함께 보내 자신이 누구인지를 증명합니다.

✅ 2. 회원가입 및 비밀번호 암호화

➕ 2-1. bcrypt 라이브러리 설치

npm install bcrypt```

#### ➕ 2-2. `User` 엔티티 수정

*   `User` 엔티티에 비밀번호 필드를 추가하고, TypeORM의 **Lifecycle Hook**인 **`@BeforeInsert()`** 또는 **`@BeforeUpdate()`**를 사용하여, 데이터가 DB에 저장되거나 업데이트되기 **직전에** 비밀번호를 자동으로 해싱하는 로직을 추가합니다.

```typescript
// user.entity.ts
import { BeforeInsert, Column, Entity } from 'typeorm';
import * as bcrypt from 'bcrypt';

@Entity()
export class User {
  // ... id, email 등 ...

  @Column()
  password: string;

  @BeforeInsert() // DB에 insert 되기 직전에 실행
  async hashPassword(): Promise<void> {
    this.password = await bcrypt.hash(this.password, 10); // 10은 salt rounds
  }

  // 로그인 시 비밀번호 비교를 위한 메서드
  async checkPassword(password: string): Promise<boolean> {
    return bcrypt.compare(password, this.password);
  }
}

➕ 2-3. 회원가입 서비스 로직

  • 서비스에서는 이메일 중복 체크 후, DTO로부터 받은 데이터를 사용하여 User 엔티티를 생성하고 리포지토리를 통해 저장합니다. 비밀번호 해싱은 엔티티의 @BeforeInsert Hook이 자동으로 처리해줍니다.

✅ 3. JWT(JSON Web Token)를 이용한 인증 시스템 구축

  • NestJS는 @nestjs/jwt 패키지를 통해 JWT 기반 인증 시스템을 매우 편리하게 구축할 수 있도록 지원합니다.

➕ 3-1. 필요한 패키지 설치

npm install @nestjs/jwt passport-jwt @types/passport-jwt

➕ 3-2. JwtModule 설정

  • users.module.ts와 같은 기능 모듈에 JwtModule.register()를 사용하여 JWT 모듈을 설정합니다.

    • secret: JWT의 서명(Signature)을 생성하고 검증하는 데 사용될 비밀 키. 이 키는 절대 외부에 노출되어서는 안 되며, 환경 변수로 관리해야 합니다.
    • signOptions: 토큰의 만료 시간(expiresIn) 등을 설정합니다.
    // users.module.ts
    import { JwtModule } from '@nestjs/jwt';
    
    @Module({
      imports: [
        JwtModule.register({
          secret: 'MY_SECRET_KEY', // 실제로는 ConfigService를 통해 환경 변수에서 가져와야 함
          signOptions: { expiresIn: '1h' },
        }),
      ],
      // ...
    })
    export class UsersModule {}

➕ 3-3. 로그인 서비스 로직

  • JwtService를 주입받아, 로그인 성공 시 JWT를 생성합니다.
  • sign() 메서드의 인자로 전달되는 Payload는, 토큰을 해독했을 때 얻게 될 데이터입니다. 보통 사용자를 식별할 수 있는 최소한의 정보(e.g., userId)를 담습니다.
// users.service.ts
import { JwtService } from '@nestjs/jwt';

@Injectable()
export class UsersService {
  constructor(
    private readonly users: Repository<User>,
    private readonly jwtService: JwtService, // JwtService 주입
  ) {}

  async login({ email, password }): Promise<string | null> {
    const user = await this.users.findOne({ where: { email } });
    if (!user) {
      return null; // 사용자가 없음
    }
    const passwordCorrect = await user.checkPassword(password);
    if (!passwordCorrect) {
      return null; // 비밀번호 불일치
    }
    // 로그인 성공, JWT 생성
    const payload = { id: user.id, email: user.email };
    const token = this.jwtService.sign(payload);
    return token;
  }
}

✅ 4. 인증된 요청 처리 (JWT Strategy)

  • 클라이언트가 JWT를 헤더에 담아 요청을 보냈을 때, 이 토큰이 유효한지 검증하고, 토큰에 담긴 사용자 정보를 요청 객체(Request Object)에 담아주는 "경비원" 역할을 하는 로직이 필요합니다. 이를 Passport.js 라이브러리와 함께 JwtStrategy로 구현합니다.
  1. JwtStrategy 생성:

    • passport-jwtStrategy를 상속받아, 토큰을 추출하고 검증하는 방법을 정의합니다.
    • validate() 메서드는 토큰 검증이 성공한 후 호출되며, 이 메서드에서 반환하는 값은 NestJS에 의해 요청 객체(e.g., req.user)에 자동으로 담깁니다.
  2. AuthGuard 적용:

    • 인증이 필요한 컨트롤러나 메서드에 @UseGuards(AuthGuard('jwt')) 데코레이터를 붙입니다.
    • 이제 해당 엔드포인트로 들어오는 모든 요청은, JwtStrategy에 의해 토큰 검증을 통과해야만 컨트롤러 로직을 실행할 수 있게 됩니다.

📌 요약

  • 사용자의 비밀번호는 bcrypt와 같은 라이브러리를 사용하여 해시(Hash)한 후 DB에 저장해야 합니다. TypeORM의 @BeforeInsert Hook을 사용하면 이 과정을 자동화할 수 있습니다.
  • JWT는 로그인 성공 시 서버가 발급하는 Stateless 인증 토큰입니다. @nestjs/jwt 모듈과 JwtService를 사용하여 JWT를 쉽게 생성할 수 있습니다.
  • 클라이언트가 보낸 JWT를 검증하는 로직은 Passport.jsStrategy 패턴을 사용하여 구현합니다.
  • JwtStrategy는 토큰의 유효성을 검사하고 사용자 정보를 반환하며, AuthGuard는 이 Strategy를 사용하여 특정 라우트를 보호하는 역할을 합니다.

0개의 댓글