본문 바로가기

Node.js

[Node.js] 계정관리 서버 프로젝트 4

 

오늘의 할 일

- 코드 보완

- 구글 클라우드 플랫폼 VM 서버 올리기

- 서버 올리기 외부 IP로 포스트맨 테스트

 

 

1. 코드보완

 

비밀번호 암호화

비밀번호를 안전하게 저장하기 위해서 해시함수를 사용하여 비밀번호를 해싱하고 저장 

 

1) bcrypt 라이브러리를 사용하여 해싱하기 위해 bcrypt 라이브러리 설치

 

2) 비밀번호를 암호화하는 hashPassword()와 비밀번호 비교를 해주는 comparePasswords 작성

var express = require("express");
var router = express.Router();

const logging = require("../util/logger"); // 로그 모듈
const bcrypt = require("bcrypt");

/*
  * bcrypt 비밀번호 암호화
  비밀번호를 암호화하는 두 가지 메서드
  hashSync(password, saltRounds): 동기적으로 비밀번호를 암호화(호출 시 현재 스레드가 block되어 작업 수행)
  hash(password, saltRounds, callback): 비동기적으로 비밀번호를 암호화(호출 시 현재 스레드가 block되지 않고 작업 수행)
*/
function hashPassword(password) {
  try {
    const hashPassword = bcrypt.hashSync(password, 10);
    return hashPassword;
  } catch (error) {
    console.log(error);
  }
}

/* 
  bcrypt 비밀번호 비교
  저장된 비밀번호와 입력된 비밀번호를 비교하는 메서드
  이 메서드 비동기적으로 동작하므로 await 또는 콜백을 사용하여 결과를 처리해야함 
*/
function comparePasswords(inputPassword, hashedPassword) {
  console.log("inputPassword", inputPassword);
  console.log("hashedPassword", hashedPassword);
  try {
    const result = bcrypt.compare(inputPassword, hashedPassword);
    return result;
  } catch (error) {
    logging.error("비밀번호 비교 실패", { error: error });
    throw error;
  }
}

module.exports = { hashPassword, comparePasswords };

 

3) 기존 코드 변경

계정추가

/* POST 계정 추가. */
router.post("/", function (req, res, next) {
  logging.info("POST/ 계정 추가", { body: req.body });

  var email = req.body.email;
  var password = bcrypt.hashPassword(req.body.password); // 해시된 비밀번호를 저장
  var name = req.body.name;
  var tel = req.body.tel;

  try {
    // 이메일 중복체크
    const existUser = account.findUserByEmail(email);
    if (existUser) {
      logging.error("이메일 중복", { email: email });
      res.status(409).send(response.fail(409, "이메일 중복")); // 409: Conflict
    } else {
      UserRealm.write(() => {
        UserRealm.create("User", {
          email: email,
          password: password,
          name: name,
          tel: tel,
          toeken: "default",
          date: new Date(),
          tokenExp: new Date(),
        });
      });
      res.status(201).send(response.success(201, "계정 추가 성공"));
    }
  } catch (error) {
    logging.error("계정 추가 실패", { error: error });
    console.log(error);
    res.status(500).send(response.fail(500, "계정 추가 실패"));
  }
});

 

로그인

로그인은 account에서 계정 인증이 된 결과로 로그인 성공/실패가 결정됨

/* POST 로그인 */
router.post("/login", async function (req, res, next) {
  logging.info("POST/ 로그인 요청", { email: req.body });

  try {
    // 계정 인증
    const result = await account.account(req, res, next);
    if (result) {
      res.status(200).send(response.success(200, "로그인 성공", result.token));
    } else {
      res.status(500).send(response.fail(500, "로그인 실패"));
    }
  } catch (error) {
    logging.error("로그인 실패", { error: error });
  }
});

account 에서 입력된 비밀번호와 기존에 저장된 비밀번호를 bcrypt.comparePassword로 비교함

email이 존재하고 그 email에 저장된 비밀번호와 일치하는 경우 계정인증됨

var UserRealm = require("../db/realm");
const uuid = require("uuid"); // uuid 모듈 추가
const logging = require("../util/logger"); // 로그 모듈
const bcrypt = require("../module/hashPasswordModule"); // 비밀번호 암호화 모듈

//------------------------------ 공통 메서드------------------------------//

// 이메일 기반 계정 조회
function findUserByEmail(email) {
  return UserRealm.objects("User").filtered(`email = "${email}"`)[0];
}

// 이메일 기반 비밀번호 조회
function findPassword(password, inputPassword) {
  return password === inputPassword;
}

//------------------------------ 계정 관련 메서드------------------------------//

async function account(req, res, next) {
  logging.info("계정 인증 요청", { 요청정보: req.body });
  const user = findUserByEmail(req.body.email);

  console.log("user.password", user.password);
  console.log("req.body.inputPassword", req.body.inputPassword);

  try {
    const passwordMatch = await bcrypt.comparePasswords(
      req.body.inputPassword, // 입력된 비밀번호
      user.password // 해시된 비밀번호
    );

    console.log(passwordMatch);

    if (user && passwordMatch) {
      UserRealm.write(() => {
        const sessionToken = uuid.v4(); // uuid를 사용해서 세션 토큰 생성
        user.token = sessionToken; // 세션 토큰 저장
      });
      logging.info("계정 인증 성공", { user: user });
      return user;
    } else {
      logging.error("계정 인증 실패", { user: user });
      return 0;
    }
  } catch (error) {
    logging.error("사용자 인증 실패", error);
    return 0;
  }
}

module.exports = { account, findUserByEmail, findPassword };

 

비밀번호 변경

로직을 생각하던 중에 현재 입력값으로 inputPassword와 newPassword를 받는 부분이 잘못됐다는 생각이 들었음

이 api는 비밀번호 변경이니까 newPassword만 받아도 되므로

기존 비밀번호를 받는건 이미 로그인에서 이메일/비밀번호를 입력해서 토큰을 받고 헤더에 있는 이메일과 토큰 검증만으로도 사용자 인증이 된다면 비밀번호 변경은 세션관리 모듈인 sessionModule을 통해 요청 헤더를 분석하여 인증하고 인증된 계정임이 확인되면 비밀번호를 변경해야하는 로직으로 수정해야하며 추가로 변경할 비밀번호를 암호화하여 저장하는 코드로 수정이 필요함

 

/*
기존 로직
쿼리스트링으로 이메일을 받고, 바디에는 현재 비밀번호와 새로운 비밀번호를 받는다.
이메일로 계정을 조회하고, 조회된 계정의 비밀번호와 바디에 입력된 현재 비밀번호를 비교한다.
비밀번호가 일치하면 새로운 비밀번호로 변경한다.
*/
router.put("/:email", function (req, res, next) {
  logging.info("PUT/ 비밀번호 변경 요청", { email: req.params.email });
  const user = account.findUserByEmail(req.params.email);
  console.log(JSON.stringify(user));
  const password = account.findPassword(user.password, req.body.inputPassword);
  console.log(user.password);
  console.log(req.body.inputPassword);
  console.log(password);
  try {
    if (user && password) {
      UserRealm.write(() => {
        user.password = req.body.newPassword;
      });

      res.status(200).send(rsponse.true(200, "비밀번호 변경 성공"));
    } else {
      res
        .status(500)
        .send(
          response.fail(500, "비밀번호 변경 실패, 비밀번호를 확인해주세요")
        );
    }
  } catch (error) {
    logging.error("비밀번호 변경 실패", { error: error });
    res.status(500).send(response.fail(500, "비밀번호 변경 실패"));
  }
});

 

수정 로직

/*
수정 로직
쿼리스트링으로 이메일을 받고, 헤더에 이메일과 토큰을 받고, 바디에는 수정할 비밀번호를 받는다.
세션 모듈로 사용자 인증을 한다.
사용자 인증이 성공하면 입력된 수정할 비밀번호로 변경한다.

*/
router.put("/:email", function (req, res, next) {
  logging.info("PUT/ 비밀번호 변경 요청", { email: req.params.email });

  // 사용자 인증
  const result = session(req, res, next);

  const user = account.findUserByEmail(req.params.email);

  try {
    if (result) {
      UserRealm.write(() => {
        user.password = bcrypt.hashPassword(req.body.newPassword); // 해시된 비밀번호를 저장
      });

      res.status(200).send(response.success(200, "비밀번호 변경 성공"));
    } else {
      res
        .status(500)
        .send(
          response.fail(500, "비밀번호 변경 실패, 비밀번호를 확인해주세요")
        );
    }
  } catch (error) {
    logging.error("비밀번호 변경 실패", { error: error });
    console.log(error);
    res.status(500).send(response.fail(500, "비밀번호 변경 실패"));
  }
});

 

 

트러블슈팅

Promise{<pending>} 은 해당 코드 블록이 비동기적인 작업을 수행하고 있음을 나타냄

await 또는 then을 사용하여 Promise가 완료될 때까지 기다려야 한다는 의미

bcrypt 라이브 러리의 대부분의 메서드는 비동기적으로 동작함

hash함수와 compare 가 해당되며, 비동기적이므로 콜백 함수를 사용하거나 Promise를 반환함

 

2. 구글 클라우드 플랫폼 VM 서버 올리기

2. GCP VM에 서버 올리기

포트 추가해서 다른프로젝트 올리기

 

Nginx 설정 파일로 만들어 놓은 configuration을 수정해야함

수정하기 위해서 텍스트 편집기를 사용해야하는데 텍스트 편집는 nano, vim, vi, emaca 등이 있음

그 중 nano를 사용해서 편집


 

 

nginx 구성파일의 유효성 검사


 

nginx 재시작

 

 

3. 외부 IP로 접속해서 포스트맨 테스트

 

​계정 전체 조회


 

 

계정추가 POST 요청

 

​​

 

 

 

예외처리 - 이메일 중복


 

 

 

로그인

 


 

 

 

 

 

비밀번호 변경 PUT 요청


 

 

로그 관리

 

포트 추가해서 다른프로젝트 올리기

 

Nginx 설정 파일로 만들어 놓은 configuration을 수정해야함

수정하기 위해서 텍스트 편집기를 사용해야하는데 텍스트 편집는 nano, vim, vi, emaca 등이 있음

그 중 nano를 사용해서 편집


 

 

nginx 구성파일의 유효성 검사


 

nginx 재시작

 

 

3. 외부 IP로 접속해서 포스트맨 테스트

 

​계정 전체 조회


 

 

계정추가 POST 요청

 

​​

 

 

 

예외처리 - 이메일 중복


 

 

 

로그인

 


 

 

 

 

 

비밀번호 변경 PUT 요청


 

 

로그 관리

 

포트 추가해서 다른프로젝트 올리기

 

Nginx 설정 파일로 만들어 놓은 configuration을 수정해야함

수정하기 위해서 텍스트 편집기를 사용해야하는데 텍스트 편집는 nano, vim, vi, emaca 등이 있음

그 중 nano를 사용해서 편집


 

 

nginx 구성파일의 유효성 검사


 

nginx 재시작

 

 

3. 외부 IP로 접속해서 포스트맨 테스트

 

​계정 전체 조회


 

 

계정추가 POST 요청

 

​​

 

 

 

예외처리 - 이메일 중복


 

 

 

로그인

 


 

 

 

 

 

비밀번호 변경 PUT 요청


 

 

로그 관리

 

포트 추가해서 다른프로젝트 올리기

 

Nginx 설정 파일로 만들어 놓은 configuration을 수정해야함

수정하기 위해서 텍스트 편집기를 사용해야하는데 텍스트 편집는 nano, vim, vi, emaca 등이 있음

그 중 nano를 사용해서 편집


 

 

nginx 구성파일의 유효성 검사


 

nginx 재시작

 

 

3. 외부 IP로 접속해서 포스트맨 테스트

 

​계정 전체 조회


 

 

계정추가 POST 요청

 

​​

 

 

 

예외처리 - 이메일 중복


 

 

 

로그인

 


 

 

 

 

 

비밀번호 변경 PUT 요청


 

 

로그 관리

 

포트 추가해서 다른프로젝트 올리기

 

Nginx 설정 파일로 만들어 놓은 configuration을 수정해야함

수정하기 위해서 텍스트 편집기를 사용해야하는데 텍스트 편집는 nano, vim, vi, emaca 등이 있음

그 중 nano를 사용해서 편집


 

 

nginx 구성파일의 유효성 검사


 

nginx 재시작

 

 

3. 외부 IP로 접속해서 포스트맨 테스트

 

​계정 전체 조회


 

 

계정추가 POST 요청

 

​​

 

 

 

예외처리 - 이메일 중복


 

 

 

로그인

 


 

 

 

 

 

비밀번호 변경 PUT 요청


 

 

로그 관리

 

포트 추가해서 다른프로젝트 올리기

 

Nginx 설정 파일로 만들어 놓은 configuration을 수정해야함

수정하기 위해서 텍스트 편집기를 사용해야하는데 텍스트 편집는 nano, vim, vi, emaca 등이 있음

그 중 nano를 사용해서 편집


 

 

nginx 구성파일의 유효성 검사


 

nginx 재시작

 

 

3. 외부 IP로 접속해서 포스트맨 테스트

 

​계정 전체 조회


 

 

계정추가 POST 요청

 

​​

 

 

 

예외처리 - 이메일 중복


 

 

 

로그인

 


 

 

 

 

 

비밀번호 변경 PUT 요청


 

 

로그 관리

 

​ㅋ

 

3. 서버 올리기 외부 IP로 포스트맨 테스트