NestJS에서 passport-local 전략을 사용할 때 필요한 패키지를 pnpm으로 설치
pnpm add passport passport-local @nestjs/passport pnpm add -D @types/passport-local
https://www.passportjs.org/packages/passport-local/
패키지용도
passport인증 미들웨어의 코어 라이브러리
passport-local로컬 전략 (아이디/비밀번호 로그인 등)
@nestjs/passportNestJS에서 Passport를 연동하기 위한 래퍼 모듈
@types/passport-localTypeScript에서 타입 지원 (개발용 -D 옵션)
1)/src/auth/strategy/local.strategy.ts
import { Injectable } from "@nestjs/common"; import { AuthGuard, PassportStrategy } from "@nestjs/passport"; import { Strategy } from "passport-local"; import { AuthService } from "../auth.service"; export class LocalAuthGuard extends AuthGuard('local') { } @Injectable() export class LocalStrategy extends PassportStrategy(Strategy, 'local') { constructor(private readonly authService: AuthService) { super(); } async validate(email: string, password: string): Promise<any> { const user = await this.authService.authenticate(email, password); return user; } }
2)/src/auth/auth.controller.ts
import { ClassSerializerInterceptor, Controller, Headers, Post, Request, UseGuards, UseInterceptors } from '@nestjs/common'; import { AuthService } from './auth.service'; import { AuthGuard } from '@nestjs/passport'; import { User } from 'src/users/entities/user.entity'; import type{ Request as ExpressRequest } from 'express'; import { LocalAuthGuard } from './strategy/local.strategy'; @Controller('auth') @UseInterceptors(ClassSerializerInterceptor) export class AuthController { constructor(private readonly authService: AuthService) {} @Post('register') registerUser(@Headers('authorization') token: string) { return this.authService.registerUser(token); } @Post('login') loginUser(@Headers('authorization') token: string) { return this.authService.login(token); } //@UseGuards(AuthGuard('local')) @UseGuards(LocalAuthGuard) @Post('login/passport') loginUserPassport(@Request() req: ExpressRequest): User { const user = req.user as User; return user; } }
3)/src/auth/auth.service.ts
import { BadRequestException, Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { User } from 'src/users/entities/user.entity'; import { Repository } from 'typeorm'; import * as bcrypt from 'bcryptjs'; import { ConfigService } from '@nestjs/config'; import { JwtService } from '@nestjs/jwt'; @Injectable() export class AuthService { constructor( @InjectRepository(User) private readonly usersRepository: Repository<User>, private readonly configService: ConfigService, private readonly jwtService: JwtService, ) {} parseBasicToken(rawToken: string) { //1)토큰을 '' 기준으로 스프릿 한 후 토큰 값만 추출하기 const basicSplit = rawToken.split(' '); console.log(' basicSplit ', basicSplit); if (basicSplit.length !== 2) { throw new BadRequestException('토큰 포멧이 잘못되었습니다.'); } //const [_, token] = basicSplit; const token = basicSplit[1]; console.log(' token ', token); //2) 추출한 토큰을 Base64 decoding 해서 이메일과 비밀번호를 나눈다. const decoded = Buffer.from(token, 'base64').toString('utf-8'); console.log(' decoded ', decoded); /// "email:password" const tokenSplit = decoded.split(':'); console.log(' tokenSplit ', tokenSplit); if (tokenSplit.length !== 2) { throw new BadRequestException('토큰 포멧이 잘못되었습니다.'); } const [email, password] = tokenSplit; console.log(' email, password ', email, password); return { email, password }; } async registerUser(rowToken: string) { const { email, password } = this.parseBasicToken(rowToken); const user = await this.usersRepository.findOne({ where: { email } }); if (user) { throw new BadRequestException('이미 가입한 이메일 입니다.'); } const hashRounds = this.configService.get<number>('HASH_ROUNDS') || 10; console.log('????hashRounds ', hashRounds); const hashedPassword = await bcrypt.hash(password, hashRounds); await this.usersRepository.save({ username: email, email, password: hashedPassword, }); return this.usersRepository.findOne({ where: { email } }); } async authenticate(email: string, password: string) { const user = await this.usersRepository.findOne({ where: { email } }); if (!user) { throw new BadRequestException('잘못된 로그인 정보입니다.'); } const isMatch = await bcrypt.compare(password, user.password); if (!isMatch) { throw new BadRequestException('잘못된 로그인 정보입니다.'); } return user; } async login(token: string) { const { email, password } = this.parseBasicToken(token); const user = await this.authenticate(email, password); const refreshTokenSecret = this.configService.get<string>( 'REFRESH_TOKEN_SECRET', ); const accessTokenSecret = this.configService.get<string>( 'ACCESS_TOKEN_SECRET', ); return { refreshToken: await this.jwtService.signAsync( { sub: user.id, role: user.role, type: 'refresh', }, { secret: refreshTokenSecret, expiresIn: '14d', }, ), accessToken: await this.jwtService.signAsync( { sub: user.id, role: user.role, type: 'access', }, { secret: accessTokenSecret, expiresIn: 300, }, ), }; } }
4)/src/auth/auth.module.ts
import { Module } from '@nestjs/common'; import { AuthService } from './auth.service'; import { AuthController } from './auth.controller'; import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from 'src/users/entities/user.entity'; import { JwtModule } from '@nestjs/jwt'; import { LocalStrategy } from './strategy/local.strategy'; @Module({ imports: [ TypeOrmModule.forFeature([ User ]), JwtModule.register({}), ], controllers: [AuthController], providers: [AuthService, LocalStrategy], exports: [AuthService], }) export class AuthModule {}
댓글 ( 0)
댓글 남기기