V-logue

[Node.js] bcrypt로 암호화된 비밀번호를 검증하는 과정에서 무한로딩 이슈 본문

Error

[Node.js] bcrypt로 암호화된 비밀번호를 검증하는 과정에서 무한로딩 이슈

보그 2022. 7. 5. 01:54

프로젝트를 진행하던 중, 비밀번호를 변경하는 api를 짤 일이 생겼고,

또 동시에 변경된 비밀번호가 제대로 잘 변경됐는지 확인해 볼 필요가 생겼다.

 

하나씩 검증해 나가는 과정에서, 비밀번호가 잘 변경됐는지도 확인해야하고

또 변경된 비밀번호로 로그인도 잘 진행된다는 점도 확인해야 하며 변경된 후의 상태에서

이전의 비밀번호로 로그인이 되는지 확인해야 했다.

 

1. 비밀번호가 잘 변경됐는지 확인하기 위해 table상의 password의 hash값이 변하는지 확인
2. 변경된 비밀번호로 로그인할 때 에러없이 잘 로그인이 진행되는지 확인
3. 이전의 비밀번호로 로그인 했을 때 발생할 수 있는 에러 확인

이제 절차로 보면 위에 적은 것과 같이 진행됐다.

 

비밀번호 변경 api 코드는 다음과 같다.

const updatePw = async(req, res) => {
  try{
    const { userId } = res.locals.user;
    if(userId === undefined){
      return res.status(401).send({ errorMessage: "로그인이 필요합니다." })
    }
    // 먼저 로그인된 user인지 확인,
    
    let { password, newPassword } = req.body;
    bcrypt.hash(newPassword, saltRounds, (err, hash) => {
      if(err){
        console.log(err)
        return res.stauts(400).send({ errorMessage: "hash에 실패했습니다."})
      } else {
        newPassword = hash;
      }
    })
    // body값으로 password와 newPassword를 불러오고
    // 변경될 비밀번호 값인 newePassword를 hashing한다.
    
    const sql = "SELECT * FROM users where userId=?"
    // users 테이블에서 userId라는 조건으로 user를 select하고
    
      db.query(sql, userId, (err, data) => {
        if(err) {
          console.log(err);
          return res.status(401).send({ errorMessage: "비밀번호를 확인해 주세요"})
          // 비밀번호가 입력되지는 않았는지, 혹은 다른 에러를 고려한다.
        } else {
          console.log("data입니다:", data)
          // 데이터값을 띄워서 제대로 table을 조회했는지 확인한 후
          if(data){
            bcrypt.compare(password, data[0].password, (err, result) => {
              if(err){
                console.log(err);
                return res.status(401).send({ errorMessage: "비밀번호가 일치하지 않습니다." })
                // compare한 비밀번호가 true가 아니라, err가 발생한다면 메세지를 내보낸다.
                
              } else {
              const sql2 = "UPDATE users SET password=? where userId=?"
              db.query(sql2, [newPassword, userId], (err, data_1) => {
                if(err) {
                  console.log(err);
                  return res.status(401).send({ errorMessage: "비밀번호 변경에 실패했습니다."})
                } else {
                  return res.status(200).send({ message :  "비밀번호 변경에 성공했습니다."})
                // 만약 data도 제대로 불러오고, compare도 true라면 users 테이블의 password를
                // newPassword로 변경한다. 참고로 [ 바뀔 변수 , 조건 변수 ] 순이다.
                // 에러가 발생하면 401에러를 내보내고, 성공하면 200을 내보낸다.
                }
              
              })
            }
          })
        }
      }
    })

  } catch(err){
    if(err){
      console.log(err)
      res.status(400).send({
        errorMessage: "비밀번호를 확인해 주세요"
        // 그래도 에러가 발생하면 400을 내보내 비밀번호가 제대로 입력됐는지 확인한다.
      })
    }
  }
}

먼저, api상으로 비밀번호를 변경한 것을 포스트맨으로 테스트해보면

 

임의의 비밀번호를 검증한 결과 비밀번호 변경에 성공했고, 또 db상으로도 제대로 해싱된 값이 변한 것을 확인했다.

 

그리고, 이제 변경된 비밀번호로 로그인을 해봐야 한다.

 

문제가 생긴점은 바로 이 지점인데,

변경된 비밀번호로 로그인하는 것은 아무런 문제가 없었으나 다른 비밀번호로 로그인 하려고 했을 때

위와 같은 Sending request...가 무한으로 계속된다는 점이었다.

const login = async (req, res) => {
  try {
    var { userId, password } = await postLoginSchema.validateAsync(req.body);
  } catch (err) {
    res
      .status(400)
      .send({ errorMessage: "아이디 또는 패스워드가 유효하지 않습니다." });
  }
  const users = [userId, password]

  try {
    const sql1 = "SELECT * FROM users WHERE userId=?";
    
    db.query(sql1, users[0] , (err, data) => {
        if (data.length) {
          console.log("data견본입니다:", data);
          console.log(data[0].password)
          console.log(password)
          
          bcrypt.compare(users[1], data[0].password, (err, result) => {
             if (result) {
              console.log("sadsadd",data[0].password)
              const payload = {
                userId: data[0].userId,
                nickname: data[0].nickname,
              };
              ......................

콘솔을 하나씩 찍어보며, 어디서 멈추는지 확인해본 결과

console.log가 마지막으로 찍힌 지점은 console.log(password) 이부분이었고,

 

그 다음에 이어지는 bcrypt.compare 부분에서 무한로딩이 생기는 것이었다.

 

코드상으로는 문제가 크게 없다고 판단하여, 오타가 있다고 생각하고 한참을 오타를 찾았지만 오타는

찾을 수 없었다.

 

그렇다면, 쿼리문을 통해 받아오는 data값이 문제인가 싶어 콘솔로 하나씩 다 찍어봤음에,

data값도 문제는 없었다.

 

data값도 문제가 아니라면, 문제는 도대체 어디 있는가에 대하여 한참을 고민하며 의미없이 값을

요리조리 바꿔보던 중 클라이언트와 서버간 요청이 계속된다는 점에 주목하여

코드 상에서 어떤 문제가 확실히 있음이라고 여기게 됐다.

 

그렇다면, bcrypt를 통해 암호화된 비밀번호를 req.body를 통해 받아온 passsword와 compare하는 부분이

문제인가 싶어, compare 대신 compareSync로 바꿔 봤지만 그대로였다.

 

그러다 든 생각이 compare하는 과정에서 무한 Sending request가 발생한다면,

compare하는 부분만 따로 진행하고 그 다음에 이어지는 길고 긴 코드들은 비교한 값을 참조하는 형식으로

짜보면 어떨까라는 생각이 들어서 코드를 바로 바꿔봤다.

 

try {
    const sql1 = "SELECT * FROM users WHERE userId=?";
    
    db.query(sql1, users[0] , (err, data) => {
        if (data.length) {
          console.log("data견본입니다:", data);
          console.log(data[0].password)
          console.log(password)
          
          bcrypt.compare(users[1], data[0].password, (err, result) => {
             if (result) {
              console.log("sadsadd",data[0].password)
              const payload = {
                userId: data[0].userId,
                nickname: data[0].nickname,
              };
              
              // 기존에 작성된 코드,


try {
    const sql1 = "SELECT * FROM users WHERE userId=?";

    db.query(sql1, users[0], (err, data) => {
      if (data.length) {
        const hashed = bcrypt.compareSync(users[1], data[0].password);
        if (!hashed) {
          return res.status(400).send({
            errorMessage: "아이디 또는 비밀번호가 일치하지 않습니다.",
          });
        } else {
          const payload = {
            userId: data[0].userId,
            nickname: data[0].nickname,
          };
          
          // 새롭게 작성된 코드

위는 기존의 코드고, 아래는 새로 작성된 코드다.

compare한 후 콜백함수를 통해 payload에 값을 넣고 jwt token을 발급하는 과정을 거치는

이런 일련의 작업들이 compare한 후 이뤄지게 되는데,

 

compare하는 부분을 따로 밖으로 빼내고, 그 다음의 token을 발급하는 과정또한 콜백의 콜백함수 안에서

돌아가는 것이 아니라, 1단계의 콜백만 거치고 바로 실행되게 코드를 바꿔봤더니

너무도 아름답게, 내가 잡아준 상태값으로 메세지가 출력됐다.

 

이번에 느낀게.. 정말로 콜백의 콜백의 콜백을 쓰는건 항상 조심해야겠다는

생각이 들었다.

 

비밀번호 변경 api도 기본적으로 콜백의 콜백의 콜백인 구조인데,

바꿀 수 있다면 조금 더 보기 편한 방식으로 구조를 변경해야 겠다는 생각이 들었다.

사용상의 편의를 위해 콜백함수를 너무 편애하다보니

문제가 생겼다고 생각했다.

Comments