[개발일지] 24.01.15

태양신 니카·2024년 1월 15일
0

개발일지

목록 보기
2/5
post-thumbnail

오늘은 지난 주에 고생했던 내용에 대해서 알아보려고 한다.

  1. 회원가입
  2. 로그인(JWT, Passport)

1. 회원가입(bcryptJS)


nestJS에서 auth 구현은 일반 nodeJS와 유사하다. 사실 장고처럼 특출나게 auth 관리를 하지 않는 이상 다른 프레임워크도 비슷한 수준인 것 같다.

회원가입시에는 User, 현재 구현하고 있는 서비스에서는 Recruiter와 Applier의 엔티티에 새로운 레코드를 추가하는 것이다.

추후 로그인 시 아이디로 businessId, 비밀번호로 password를 사용하기 때문에 회원가입 시 businessId가 겹치게 된다면 오류를 내보내도록 엔티티 단에서 @Unique(['businessId'])를 설정해주었다.

@Entity()
@Unique(['businessId'])
export class Recruiter {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  businessId: string;

  @Column()
  password: string;
  ...
}

비밀번호를 신규회원이 입력한 그대로 데이터베이스에 저장하게 된다면 보안상 심각한 문제를 초래할 수 있다. 따라서 bcryptJS 모듈을 사용하여 해시된 비밀번호를 데이터베이스에 저장하도록 하였다.

async createRecruiter(signUpRecruiterDto: SignUpRecruiterDto): Promise<void> {
    const { businessId, password, managerName, companyName } =
      signUpRecruiterDto;
    const salt = await bcrypt.genSalt();
    const hashedPassword = await bcrypt.hash(password, salt);
...
}

salt를 사용해서 일반적인 decrypt가 이뤄지지 않도록 하여 보안 수준을 높였다.

2. 로그인(JWT, Passport)


로그인 시 JWT로 토큰을 발급하고 엔드포인트마다 발급된 토큰을 가지고 권한을 확인하는 Passport + UseGuards 방식을 활용하여 권한 인가를 설정하였다.

JWT(JSON Web Token)은 Header, Payload, Signature 세 부분으로 구성된다. Header에는 주로 해시 알고리즘에 대한 정보가 들어있고 Payload에는 사용자와 관련된 정보가 들어있고 토큰 생성 설정을 통해 구성을 변경할 수 있다. Signature에는 헤더와 페이로드의 인코딩된 정보와 비밀키가 들어있다.

JWT관련 정보는 auth 모듈에서 import 하여 등록한다.

@Module({
    imports: [TypeOrmModule.forFeature([Recruiter, Recruitment, Applier]),
JwtModule.register({
    secret: Constant.secret,
    signOptions: {
        expiresIn: '3600s',
    },
}), PassportModule.register({ defaultStrategy: 'jwt'})],
    controllers:[AuthController],
    providers: [AuthService, RecruiterService, ApplierService, JwtStrategy],
    exports: [JwtStrategy, PassportModule], //다른 모듈에서도 사용해야하므로 추출해야함
})
export class AuthModule {}

secret에 대한 문자열은 외부로 노출되면 안되기 때문에 반드시 gitIgnore로 관리를 해주어야한다.

passport는 nodeJS에서 인증과 관련된 유명한 미들웨어 중 하나이다. 일반적인 로그인부터 OAUTH까지 구현할 수 있다. 이번 프로젝트에서는 로그인은 직접 따로 service에서 구현을 하였다. JWT에 해당하는 부분만 strategy를 따로 지정하여 @UseGuards(AuthGuard())로 인증을 관리한다. 다음은 jwt.strategy.ts의 일부이다.

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(private recruiterService: RecruiterService,
    private applierService: ApplierService) {
    super({
      secretOrKey: Constant.secret,
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
    });
  }

  async validate(payload) {
    const { businessId, userType } = payload;

    if(userType === 'recruiter'){
        ...

    }else if(userType === 'applier'){
        ...
    }
  }
}

strategy에서 생성자쪽에 문제가 없으면 validate 함수가 실행되면서 인증 여부를 평가한다.

또하나 추가적으로 한 것은 @UseGuards()로 request에 실린 user 정보를 가져오기 위한 커스텀 데코레이터를 만든 것이다. 다음과 같이 만들었다.

//get-user.decorator.ts
export const GetUser = createParamDecorator(
  (data, ctx: ExecutionContext): Recruiter | Applier => {
    const req = ctx.switchToHttp().getRequest();
    const { userType, ...user} = req.user;//jwt strategy에서 넣은 userType을 다시 제거해야 쿼리가 정상적으로 동작함
    return user; //default로 유저정보가 들어간 것은 user로 되어있기 때문에 이에 맞게 해야함.
  },
);

위 과정을 통해 recruiter와 applier별로 엔드포인트의 접근 권한을 구별하여 지정할 수 있었다.

profile
원피스를 찾아서

0개의 댓글

관련 채용 정보