될때까지

((TIL)) Node.js express로 인스타그램 구현하기2 본문

학습/Node.js

((TIL)) Node.js express로 인스타그램 구현하기2

랖니 2022. 9. 14. 22:58
728x90

🌸 intro

오늘은 회원가입을 구현했다.

  1. 이메일 양식 검증
  2. 비밀번호 양식 검증
  3. 이메일 중복 => 409 에러
  4. 비밀번호 암호화 => bcrypt사용 
  5. 관심사 분리 원칙 + mvc패턴 적용
    1. router -> controller -> service -> model 순으로 간다.
    2. router : url을 기능과 연결해주는 부분
    3. controller : 사용자의 요청을 받아 서비스로 전달하고 응답을 받아 프론트로 전달한다.
      프론트와 백을 연결하는 어댑터역할
    4. service : 컨트롤러에서 넘어온 데이터를 검증하는 비즈니스 로직을 작성하고 모델로 넘겨준다.
    5. model : DB와 연결해서 CRUD하기 위한 로직을 구현하고 컨트롤러에게 전달한다.
    6. 맡은 기능만 구현해야한다. 모델에서 요청을 처리하거나 컨트롤러에서 비즈니스 로직 작성 노노.
      스파게티 코드가 될 우려가 있다.

 

 

🧚🏻‍♀️ 클라이언트로부터 데이터 받는 방법

1. query parameter

const {userId, password} = req.query;

2. path parameter

router.get("/:userId", (req, res, next) => {
	const {userId} = req.params;
    console.log(userId);   // {userId : 3}
    // 3을 사용하고 싶다면 req.params.userId해야함.
})

3.  body

const {userId, password} = req.body;

 

🧚🏻‍♀️ 노드에서 정규식 사용하기

정규표현식은 문자열에 포함된 특정 문자열을 조작하기 위해 사용하는 패턴으로, 사용자가 입력한 데이터값을 검증하고 싶을 때 사용할 수 있다. 예를 들어 회원가입 시 이메일양식/비밀번호 양식처럼.

노드에서는 RegExp 객체의 생성자를 호출해서 사용할 수 있다.

const re = new RegExp('ab+c')

문자열에 일치하는 부분이 있는지 여부는 test()라는 메소드를 사용해 확인 가능하며, true 또는 false값을 반환한다. 이를 코드에 적용시켜보면 아래와 같다.

const validateEmail = (email) => {
    const emailValidation = new RegExp(
        /^[0-9a-zA-Z]([-_\.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_\.]?[0-9a-zA-Z])*\.[a-zA-Z]{2,3}$/
    );

    console.log('emailValidation.test(email)', emailValidation.test(email))  // 일치하면 true , 틀리면 false 리턴

    if (!emailValidation.test(email)) {
        throw {status:400, message:"이메일 양식 탈락"}
    }
};

 

 

🧚🏻‍♀️ DAO ? DTO ?

노드로 프로젝트를 할 때, models 폴더 안에 userDao.js를 만들고 그 안에서 데이터를 CRUD하는 쿼리문을 작성했다.
인턴쉽 때 controllers폴더 안에 userController.js를 만들고 body에 데이터를 담아 요청할 때, req.body에 담긴 데이터를 userDto라는 변수에 저장했다. DAO와 DTO가 뭐길래 구분해서 사용하는 걸까 궁금해서 공부해봤다.

  • DTO : Data Transfer Object의 약자로 데이터를 서비스나 컨트롤러로 보낼 때 사용하는 객체다. 로직이 없는 순수한 데이터 객체 그 자체라고 생각하자.
  • DAO : Data Access Object의 약자로 서비스와 DB를 연결해주는 객체다. 

그래서 req.body에 담긴 데이터를 저장할때 DTO이름을 사용해 컨트롤러에서 서비스로 넘겨줬고, 모델단에서 데이터를 CRUD하는 쿼리문을 작성할때 해당 파일의 이름을 DAO로 작성했었던 것이군🤓

 

 

🧚🏻‍♀️ 자바스크립트에서 [ ] 빈 배열은 truthy값이다.

const signUp = async (email, password) => {

    validateEmail(email)
    validatePassword(password)

    const user = await userDao.getUserByEmail(email);
    // console.log('서비스에서 유저', user)   
    // => 여기서 DB에 존재하지 않는 이메일,비밀번호를 입력한 뒤에
    // user를 찍어보면 []이 찍힌다. 자바스크립트에서 []는 truthy이므로 아래 코드가 계속 실행돼서 오류를 뱉어냈다.

    if (user) {
        throw {status:409, message:"이메일 중복"}
    }

    const hashedPassword = await bcrypt.hash(password, 10);
    await userDao.createUser(email, hashedPassword);
}

해당 코드를 그래서 아래와 같이 변경했다. 그랬더니 잘 동작하는 코드👍

    if (user.length === 0) {
        throw {status:409, message:"이메일 중복"}
    }

 

🧚🏻‍♀️ 비밀번호 암호화(Bcrypt)

대표적인 해시함수(해시함수=복호화 할 수 없는 알고리즘)인 SHA256은 원래 빠른 시간안에 데이터를 검색하기 위한 자료구조로 설계가 됐다. 그렇다 보니 빠른 게 오히려 단점이라지 이 빠름을 이용해 Rainbow table attack(해시함수를 사용해서 변환 가능한 모든 해시값을 테이블로 만든 것)을 해서 비밀번호를 알아낼 수 있기 때문이다.

이러한 취약점을 보완하기 위해 bcrypt가 나왔다.
bcrypt는 솔트(salt)와 키 스트레칭(key-stretching) 방식을 도입한 비밀번호 암호화 함수다.

  • salt : 입력한 비밀번호 평문에 무작위 데이터를 소금치듯 더한다.
             똑같은 비밀번호여도 솔트값에 따라 해시값이 달라지기 때문에 rainbow table attack 방어 성공!
  • key-stretching : 단방향 해시값을 몇번이고 반복해서 계속 해싱하는 방법이다.
                                이로 인해 빠른 속도의 취약점을 보완할 수 있다.

그렇다면 로그인할 때 비밀번호 검증은 어떻게 할까?

  • 사용자가 입력한 비밀번호를 salt + key-stretcing한 뒤에 DB에 저장된 문자열과 비교한다.

사용 방법

npm install bcrypt
const bcrypt = requrie("bcrypt");

const saltRounds = 10;     
// salt가 몇글자인지 지정할 수 있다. => 10자리의 salt를 사용하겠다는 뜻
// 높아질수록 보안이 높아지지만 속도 느려짐

// 암호화
bcrypt.hash(입력된비밀번호, saltRounds)

// 비교
bcrypt.compare(입력된비밀번호, DB에 저장된 비밀번호);

비밀번호 암호화는 hashSync or hash가 있고 비밀번호 검증에는 compareSync or compare가 있다.
둘의 차이점을 아래 블로그에서 참고했는데 전혀 이해가 되지 않았다... 동기/비동기 다시 공부해야겠다. 일단 첨부해놓기

from https://gofnrk.tistory.com/112

 

🧚🏻‍♀️  Rebase 왜 안돼?

분명히 squah를 진행해서 작업했는데 왜 merge commit이 남았을까...!!! 아잇 안예뻐
실습해보자 => 실습 내용은 해당 포스팅 참고

728x90