// jwt.strategy.ts
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { User } from '../user/entities/user.entity';
import * as config from 'config';
import { UserRepository } from 'src/user/user.repository';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
@InjectRepository(UserRepository)
private userRepository: UserRepository,
) {
super({
secretOrKey: process.env.JWT_SECRET || config.get('jwt.secret'),
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
});
}
// 페이로드에 있는 username을 조회하여 user 객체를 return합니다.
async validate(payload) {
const { username } = payload;
const user: User = await this.userRepository.findByUsername(username);
if (!user) {
throw new UnauthorizedException('존재하지 않는 유저입니다.');
}
if(!user.isVerified) {
throw new ForbiddenException('서비스 이용이 승인되지 않은 유저입니다.')
}
return user;
}
}
FeedDaMoA 프로젝트를 진행하면서 서비스 사용을 제한할 때, 서비스를 이용 가능한지 확인하는 로직을 어디에 작성하는 것이 좋을지 고민했습니다. 그 결과, 컨트롤러에 유저 정보를 확인해서 차단 하는 것이 좋다고 생각합니다.
그 이유는 middleware에서 서비스가 이용가능한지 확인하여 추가적인 함수 실행을 방지하는 것이 타당하기 때문입니다.
JWT 전략 파일에서 validate() 함수에 username으로 DB를 조회하여 유저 객체를 반환하고 서비스 이용이 가능한 유저인지 판단합니다.
validate() 함수는 Passport에서 JWT 전략을 사용할 때 실행되는 함수로, JWT가 들어오면 매 요청마다 호출됩니다. 구체적으로는 JwtStrategy 클래스의 validate() 함수는 사용자가 요청을 보낼 때마다 실행됩니다.
여기서 간단한 흐름은 다음과 같습니다.
클라이언트가 서버로 요청을 보낼 때, 헤더에 JWT 토큰을 포함시킵니다. 이는 주로 Bearer Token 형태로 Authorization 헤더에 들어갑니다.
Passport는 요청이 들어올 때마다 설정된 전략에 따라 토큰을 추출하고(jwtFromRequest 설정), validate() 함수를 호출하여 토큰의 유효성을 확인하고 해당 사용자를 찾아냅니다.
validate() 함수에서는 JWT의 페이로드에서 사용자 식별 정보(예: username)를 추출하고, 해당 정보를 사용하여 데이터베이스에서 사용자를 조회합니다.
조회한 사용자가 존재하면, 해당 사용자 객체가 validate() 함수에서 반환됩니다.
반환된 사용자 객체는 JwtStrategy 클래스의 인스턴스를 생성할 때 등록된 AuthService 클래스의 validate 메서드로 전달되며, AuthService에서 이 사용자 객체를 활용하여 로그인 등의 기능을 수행할 수 있습니다.
따라서 매 요청마다 클라이언트의 JWT 토큰을 검증하고, 해당 사용자 정보를 확인하기 위해 validate() 함수가 실행됩니다.