보안과 인증
Passport
passport는 미들웨어를 구현하기 위한 패키지로 간단하고 유연한 방식으로 사용자 인증을 관리하고 처리할 수 있도록 도와주며, 다양한 인증 전략(Strategy)을 제공하여 여러 인증 방식을 지원
사용자 인증 방식으로는 로컬(Username/Password), 소셜 로그인(Google, Facebook, Twitter 등), OAuth, JWT(JSON Web Token) 등 다양한 방법을 Passport를 통해 구현가능
1.local 인증
패키지 설치
npm install @nestjs/passport passport @types/passport-local passport-local @types/express
@nestjs/passport: NestJS에서 Passport를 사용하기 위한 모듈. NestJS와 Passport를 연결해주는 역할
passport: Passport 패키지
@types/passport-local: Passport 패키지를 TypeScript로 사용하기 위한 타입 선언 파일.
passport-local: 로컬 인증(Local Authentication) 전략을 제공하는 Passport의 전략(strategy) 중 하나.
@types/express: Express.js를 TypeScript로 사용하기 위한 타입 선언 파일.
다음으로 local-auth.strategy.ts 파일을 만든다
@Injectable()
export class LocalAuthStrategy extends PassportStrategy(Strategy){
constructor(
private authService: AuthService;
){
super({ usernameField : 'email' } //상위 클래스(PassportStrategy)의 생성자를 호출
}
// PassportStrategy를 상속받은 커스텀 전략에서 반드시 구현해야 하는 validate 메서드.
// 사용자의 인증을 수행하는 로직을 구현
async validate( email:string, password: string ): Promise<User>{
return await this.authService.userLogin({ email, password });
}
}
생성된 strategy class를 auth.module provider 에 추가한다.
인증과 관련된 로직을 미들웨어로 분리하여 코드를 구조화하고 중복을 제거하기 위해 local-auth.guard.ts 파일도 생성해준다.
@Injectable()
export class LocalAuthGuard extends AuthGuard('local'){}
// AuthGuard 생성자에서 사용할 Strategy를 인자로 넘겨줌으로 LoaclAuthStrategy에 구현한 로직이 반영된다.
적용
auth.controller 에 strategy를 적용
인증을 사용할 api 매소드에 다음과 같이 추가해준다.
@Post('login')
@UseGuards(LocalAuthGuard)
async userLogin(
@Body()
loginUserDto:LoginUserDto
){
return await this.authService.userLogin(loginUserDto);
}
2.토큰생성
토큰 생성에는 JWT( JSON Web Token 웹 표준(RFC 7519)으로서, 정보를 안전하게 전달하기 위해 JSON 형식을 사용하는 토큰)을 사용
패키지 설치
npm install @nestjs/jwt passport-jwt @types/passport-jwt cookie-parser @types/cookie-parser
@nestjs/jwt: NestJS 애플리케이션에서 JWT(JSON Web Token) 인증을 구현하기 위한 패키지
passport-jwt: PassportJS를 사용하여 JWT 인증 전략을 구현하는 패키지
@types/passport-jwt: TypeScript를 사용하는 경우, passport-jwt 패키지의 타입 정의를 제공
cookie-parser: HTTP 요청에서 쿠키를 파싱하여 사용할 수 있게 해주는 미들웨어 패키지
@types/cookie-parser: TypeScript를 사용하는 경우, cookie-parser 패키지의 타입 정의를 제공
패키지 설치가 끝나면 auth.module에 ConfigModule(.env에 토큰관련 값을 저장/관리해야함), PassportModule, JwtModule.register({}) 를 import 시킨다.
토큰값은 민감한 정보이기 때문에 전에 생성해놓은 .env 파일에 추가한다
JWT_ACCESS_TOKEN_SECRET= xxx
JWT_ACCESS_TOKEN_EXPIRATION_TIME=10
app.module에 ConfigModule 유효성검사 부분에 추가한 항목도 반영해주어야함
-토큰 생성
//tokenPayload.interface.ts 파일생성 (토큰에 담기는 데이터)
export interface TokenPayload{
userId:string;
}
//
//토큰 생성 함수 (auth.service.ts)
+
//생성자에 jwtService 와 configService를 선언해주어야함
async generateToken(userId:string):Promise<string>{
const payload: TokenPayload = { userId }; //jwt.Service.sign에 넘겨줄 인자
const token = this.jwtService.sign(payload,{
secret : this.configService.get('JWT_ACCESS_TOKEN_SECRET'),// 토큰을 암호화 하는데 필요한 비밀키
expiresIn : `${this.configService.get('JWT_ACCESS_TOKEN_EXPIRATION_TIME')}h`//뒤에 h는 시간단위를 의미
});
return token;
}
-토큰으로 유저 조회
jwt-auth.guard.ts/jwt-auth.strategy.ts 생성
@Injectable()
export class JwtAuthStrategy extends PassportStrategy(Strategy){
constructor(
private readonly userService: UserService,
private readonly configService: ConfigService,
){
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),//클라이언트에서 서버로 전송된 JWT 토큰을 어떻게 추출할지를 지정
secretOrkey: configService.get('JWT_ACCESS_TOKEN_SECRET')//토큰의 유효성을 검증하는데 사용되는 비밀키(secret key)를 지정
});
}
async validate(payload: TokenPayload): Promise<User>{
return await this.userService.getUserById(payload.userId)
}
}
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt'){}
-requestWithUser.interface.ts 파일 생성
기존 Request에서 요청 객체를 확장(+User 클래스)하여 사용하기 위해
export interface RequestWithUser extends Request {
user: User;
}
-api method 생성
auth.controller.ts에 다음 함수 추가
+
@Get()
@UseGuards(JwtAuthGuard)
async authedUser(
@Req()
req: RequestWithUser
){
const user = req.user;
return user;
}