DevBoi

[NestJs] AuthGuard 전략 복수개 적용하기 본문

Develop/[NestJs]

[NestJs] AuthGuard 전략 복수개 적용하기

HiSmith 2023. 7. 4. 22:16
반응형

오늘은 AuthGuard를 이용하여, 해당 가드를 적용할건데

각기 다른 정책으로 관리가 되게 개발을 할 것이다.

쉽게 얘기하면, 한개는 일반 사용자인지, 즉 auth bearer token으로 요청을 하는지

또 한개는 해당 사용자이지만, 사업가의 authCode인지를 체크하는 것이다.

 

적용한 코드를 참고 바란다.

 

1.패키지 구조

 

2.auth-guard.ts

import { ExecutionContext, Injectable } from "@nestjs/common";
import { AuthGuard as NestAuthGuard } from "@nestjs/passport";
import { Observable } from "rxjs";

@Injectable()
export class JwtAuthGuard extends NestAuthGuard('member'){
    
    canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {                
        return super.canActivate(context)
    }
}

3.auth-module

import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { JwtStrategy } from './passport.jwt.strategy';
import { MemberService } from 'src/member/service/member.service';
import { MemberRepostiory } from 'src/member/repository/member.repository';
import { Member } from 'src/member/entities/member.entity';
import { CorpJwtStrategy } from './passport.jwt.corp.strategy';

@Module({
    imports: [
      PassportModule.register({defaultStrategy: 'jwt'}),
      TypeOrmModule.forFeature([Member]),
      JwtModule.register({
        secret: 'sadfasdfasfasdfdasfasfdads',
        signOptions: {expiresIn: '300s'},
      }),
      PassportModule
    ],
    exports: [TypeOrmModule],
    controllers: [AuthController],
    providers: [AuthService, MemberService,JwtStrategy,MemberRepostiory,CorpJwtStrategy]
  })
  export class AuthModule {}

4. auth.service

import { HttpException, HttpStatus, Injectable, UnauthorizedException } from '@nestjs/common';
import { UserDTO } from '../user/dto/auth-user.dto';
import { User } from '../user/entities/user.entity';
import { UserService } from '../user/user.service';
import * as bcrypt from 'bcrypt';
import { Payload } from './payload.interface';
import { JwtService } from '@nestjs/jwt';
import { MemberReqDto } from 'src/member/dto/member.req.dto';
import { MemberService } from 'src/member/service/member.service';
import { Member } from 'src/member/entities/member.entity';
import { MemberRepostiory } from 'src/member/repository/member.repository';


@Injectable()
export class AuthService {
    constructor(
        private memberService: MemberService,
        private jwtService: JwtService

    ){}
    //회원가입
    async registerUser(memberReqDto: MemberReqDto): Promise<Member> {        
        await this.transformPassword(memberReqDto);
        return this.memberService.save(memberReqDto);
    }
    //로그인
    async validateUser(memberReqDto: MemberReqDto): Promise<{accessToken: string} | undefined> {        
        const member = await this.memberService.findOne(memberReqDto.id);
        const validatePassword = await bcrypt.compare(memberReqDto.password,member.password);
        
        if(!member || !validatePassword) {
            throw new UnauthorizedException();
        }
        const payload: Payload = { id: memberReqDto.id, password: memberReqDto.password};
        return {
            accessToken: this.jwtService.sign(payload)
        };
        
    }
    async tokenValidateUser(payload: Payload): Promise<Member| undefined> {
        return this.memberService.findOne(payload.id);        
    }
    async corptokenValidateUser(payload: Payload): Promise<Member| undefined> {
        return this.memberService.findOne(payload.id);        
    }
    async transformPassword(member: MemberReqDto): Promise<void> {
        member.password = await bcrypt.hash(
            member.password, 10,
        );
        return Promise.resolve();
    }
}

 

4. passport-jwt-corp stragety

import { Injectable, UnauthorizedException } from "@nestjs/common";
import { PassportStrategy } from "@nestjs/passport";
import { Strategy,ExtractJwt,VerifiedCallback } from "passport-jwt";
import { AuthService } from "./auth.service";
import { Payload } from "./payload.interface";

@Injectable()
export class CorpJwtStrategy extends PassportStrategy(Strategy,'corp'){
    constructor(private authService : AuthService ){
        super({            
            jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
            ignoreExpiration : true,
            secretOrKey: 'sadfasdfasfasdfdasfasfdads'
        })
    }
    async validate(payload: Payload, done: VerifiedCallback): Promise<any> {        
        console.log('corp')
        const user = await this.authService.corptokenValidateUser(payload);     
        console.log(user);
        //authCode가 10이면 사업자가 아니라 일반 사용자라고 가정한다.
        if(!user) {
            throw new UnauthorizedException({message: 'user does not exist'});
        }
        if(user.authCode==10){
            throw new UnauthorizedException({message: 'this is not corp user'});
        }
        return user;
    }

}

5. passport.jwt.stragety

import { Injectable, UnauthorizedException } from "@nestjs/common";
import { PassportStrategy } from "@nestjs/passport";
import { Strategy,ExtractJwt,VerifiedCallback } from "passport-jwt";
import { AuthService } from "./auth.service";
import { Payload } from "./payload.interface";

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy,'member'){
    constructor(private authService : AuthService ){
        super({
            jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
            ignoreExpiration : true,
            secretOrKey: 'sadfasdfasfasdfdasfasfdads'
        })
    }
    async validate(payload: Payload, done: VerifiedCallback): Promise<any> {        
        const user = await this.authService.tokenValidateUser(payload);  
        console.log(user);
        if(!user) {
            throw new UnauthorizedException({message: 'user does not exist'});
        }
        return user;
    }

}

6. payload.interface

export interface Payload {
    id: string;
    password: string;
}

7. auth-controller

 

import { Body, Controller, Get, Post, Req, Res, UseGuards } from '@nestjs/common';
import { Request, Response } from 'express';
import { AuthService } from './auth.service';
import { UserDTO } from '../user/dto/auth-user.dto';
import { JwtAuthGuard } from './auth-guard';
import { ApiTags, ApiOperation, ApiResponse, ApiQuery, ApiParam, ApiBody } from '@nestjs/swagger';
import { CreateUserDto } from 'src/user/dto/create-user.dto';
import { MemberReqDto } from 'src/member/dto/member.req.dto';
import { CorpJwtAuthGuard } from './auth-corp-guard';

@ApiTags('auth') // (옵션) 태그 추가
@Controller('auth')
export class AuthController {
  constructor(private authService: AuthService) {}

  @ApiOperation({ summary: '회원 가입' }) // (옵션) API 동작 설명
  @ApiBody({ 
    description: 'User registration data',
    type: MemberReqDto, // UserDTO 타입 지정
  })
  @Post('/register')
  async registerAccount(@Body() memberReqDto: MemberReqDto): Promise<any> {
    return await this.authService.registerUser(memberReqDto);
  }

  @ApiOperation({ summary: '유저 로그인' }) // (옵션) API 동작 설명
  @Post('/login')
  async login(@Body() MemberReqDto: MemberReqDto, @Res() res: Response): Promise<any> {
    const jwt = await this.authService.validateUser(MemberReqDto);
    res.setHeader('Authorization', 'Bearer ' + jwt.accessToken);
    return res.json(jwt);
  }

  //샘플 동작 모듈 헤더에 첨부된 accesskey가 올바르지 않으면, 오류가 발생한다.
  @ApiOperation({ summary: '유저 인증' }) // (옵션) API 동작 설명
  @Post('/authenticate')
  @UseGuards(JwtAuthGuard)
  isAuthenticated(@Req() req: Request): any {
    return "success";
  }
  @ApiOperation({ summary: '유저 인증' }) // (옵션) API 동작 설명
  @Post('/authenticate-corp')
  @UseGuards(CorpJwtAuthGuard)
  isAuthenticatedcorp(@Req() req: Request): any {
    return "corp_success";
  }
}

 

 

쉽게 동작과정을 살펴보면 아래와 같다.

 

1. authcontroller로 회원가입을 한다.

2. authcontroller로 로그인을 한다-> 이때 회원정보의 유효성을 검증한다.

검증후에, 해당 관련 비밀번호와 아이디가 맞다면, 해당 payload에 값을 실어서 access-token을 발급한다.

3. 해당 authenticate나 -corp를 호출하게 되면,

4.해당 authguard로 인해, 각 전략의 밸리데이트 메소드로 가게 되고, 해당 메소드에서 예외를 던지거나 처리를 마무리하여 리턴하고 메소드의 로직이 돈다.

 

<정리>

JwtAuthGuard , CorpJwtAuthGuard 이 두개는 해당 각 가드의 이름을 별도로 설정하여,

해당 가드를 맞게끔 따라가도록 설정을 해주었기 때문에 분기 처리가 가능하다.

대부분 여러가지 역할이 있을 것이고, 해당 역할에 따라서 다르게 인증 처리를 할 것인데

해당과 같이 관리를 하게 되면 편하게 두가지 분기처리로 나눠서 개발을 진행할 수 있다.

 

 

 

반응형