🖥️Frontend/Node

[Node] 회원 로그인, 비밀번호 복호화 및 JWT 토큰 생성

뉴발자 2023. 11. 29.
728x90

 

 

 

 

 

 

 

 

 

 

 

 

 

 

그림 1-1. JWT

 

 

비밀번호 암호화

https://tlseoqja.tistory.com/51

 

[Node] 회원 가입 시 비밀번호 암호화

MongoDB 연동 및 회원 가입 기능 구현 https://tlseoqja.tistory.com/49 [Node] node.js 서버와 MongoDB 연동하기 (mongoose) Mongoose란? Mongoose 라이브러리는 MongoDB란 NoSQL DB를 Node.js에서 사용할 수 있게 도와주는 라이

tlseoqja.tistory.com

 

 

JWT

JWT(Json Web Token)은 Json 객체에 인증이 필요한 정보들을 담은 후 비밀키로 서명한 토큰이다.

 

웹 표준을 따르고 있으며, 공식적으로 인증(Authentication) & 허가(Authorization) 방식으로 사용된다.

 

필요한 모든 정보를 하나의 객체에 담아서 전달하기 때문에 JWT 한 가지로 인증을 마칠 수 있다.

 

JWT의 장점 중 하나로 웹 표준을 따르기 때문에 대부분의 언어가 JWT를 지원한다.

 

대부분 사용자 인증과 로그인 유지 위해 사용한다.

 

더욱 자세한 내용은 아래 블로그를 참고하면 된다.

https://velog.io/@chuu1019/%EC%95%8C%EA%B3%A0-%EC%93%B0%EC%9E%90-JWTJson-Web-Token

 

😎 알고 쓰자, JWT(Json Web Token).

jwt 토큰이 어떻게 구성되어있는지, 왜 jwt 토큰을 사용해야하는지 아시나요?

velog.io

 

 

로그인 시 비밀번호 복호화 및 일치 확인

index.js 회원 가입 밑에 로그인 코드를 작성해준다.

// index.js

...

app.post("/api/users/register", (req, res) => {
  ...
});

app.post("/api/users/login", (req, res) => {
  
});

app.listen(port, () => console.log(`Example app listening on port ${port}!`));

 

우선 작성해야할 로직은 다음과 같다.

 

  1. 입력받은 email 정보가 DB에 존재하는지 여부를 판단
  2. 요청된 email이 있다면 비밀번호가 일치하는지 확인
  3. 비밀번호가 맞다면 토큰 생성
  4. 토큰 저장

 

1. email 정보 DB 존재 여부 판단

User 스키마의 함수인 findOne()을 사용해서 DB의 email 존재 여부를 판단한다.

// index.js
app.post("/api/users/login", (req, res) => {
  User.findOne({ email: req.body.email })
    .then(( user ) => {
      if( !user ) {
        return res.json({
          loginSuccess: false,
          message: "해당 이메일 정보가 없습니다.",
        });
      }
    });
});

app.listen(port, () => console.log(`Example app listening on port ${port}!`));

 

해당 api로 데이터를 보내서 테스트해본다.

 

이메일이 존재하는 경우 다음 로직을 작성하지 않아서 포스트맨에서 무한로딩에 걸릴 것이고,

 

이메일이 존재하지 않는 경우 리턴된 json 값이 포스트맨에 찍힐 것이다.

그림 2-1. email 미존재 시 json 리턴

 

 

2. email 존재 시 비밀번호 일치 확인

email이 존재한다면 입력받은 비밀번호가 일치하는지 확인하는 작업이 필요하다.

 

하지만 입력받은 비밀번호와 DB 내의 비밀번호는 비교하지 않아도 다를 것이다.

 

이유는 비밀번호를 암호화한 후 DB에 등록했기 때문이다.

 

 

그렇다면 입력받은 비밀번호와 복호화된 DB 비밀번호를 비교하는 동작이 필요하다.

 

다음은 스키마에서 비밀번호 비교 함수를 생성하고, 로그인 시 사용하는 방법이다.

 

 

2-1. 비밀번호 비교 함수 생성

User.js에서 이전에 작성한 pre() 함수 밑에 다음 코드를 추가해준다.

// User.js
...

userSchema.pre("save", function(next) {
  ...
});

userSchema.methods.comparePassword = function(plainPassword, cb) {
  // 입력받은 비밀번호: 1234, 암호화된 비밀번호: $2b$10$Os6KqEFfTp9P9pM.S8Ddp.skwg0ehBla1JXl9AhkUn8JGppm7LQ3m
  bcrypt.compare(plainPassword, this.password ,function(err, isMatch) {
    if( err ) return cb(err);

    cb(null, isMatch);
  });
};

 

입력받은 비밀번호와 DB의 암호화된 비밀번호를 bcrypt 라이브러리를 이용해서 비교하는 함수이다.

 

비교 후 callback 함수를 통해 에러가 있을 경우 err만을 리턴하고,

 

비밀번호가 일치할 경우 isMatch를 리턴해준다.

 

 

2-2. 비밀번호 비교 함수 적용

index.js에서 email 정보가 존재한 경우 비밀번호 함수 로직을 실행하도록 코드를 작성한다.

// index.js

app.post("/api/users/login", (req, res) => {
  // 요청된 이메일을 DB에서 존재하는지 확인
  User.findOne({ email: req.body.email })
    .then(( user ) => {
      if( !user ) {
        return res.json({
          loginSuccess: false,
          message: "해당 이메일 정보가 없습니다.",
        });
      }

      // 요청된 이메일이 DB에 있다면 비밀번호가 일치하는지 확인
      user.comparePassword(req.body.password, (err, isMatch) => {
        if(!isMatch) {
          return res.json({
            loginSuccess: false,
            message: "비밀번호가 일치하지 않습니다.",
          });
        }
    });
  });
});

 

포스트맨으로 비밀번호를 틀리게 입력한 후 보냈을 때의 화면이다.

그림 2-2. 비밀번호 불일치 시 json 리턴

 

728x90

 

 

3. 비밀번호 일치 시 JWT 토큰 생성

email이 존재하고 비밀번호가 일치한다면 로그인이 된 상태라고 할 수 있다.

 

앞서 언급한 사용자 인증과 로그인 상태를 관리하기 위해 JWT를 발급을 진행한다.

 

JWT 라이브러리를 설치해준다.

npm install jsonwebtoken

 

 

3-1. JWT 토큰 생성 함수 작성

JWT 토큰을 생성하기 위해 User.js에 다음 코드를 작성해준다.

// User.js
import jwt from "jsonwebtoken";

...

userSchema.methods.comparePassword = function(plainPassword, cb) {
  ...
};

userSchema.methods.generateToken = function(cb) {
  // jsonwebtoken을 이용해서 토큰 생성
  const user = this;

  const token = jwt.sign(user._id.toHexString(), "secretToken");

  user.token = token;

  user.save()
  .then((user, err) => {
    if( err ) return cb(err);

    cb(null, user);
  });
};

 

JWT의 sign함수를 통해 DB에서 생성된 _id값과 시크릿 키를 이용해서 JWT 토큰을 생성했다.

 

생성된 토큰은 user 객체의 token 값에 넣어서 save해주었다.

 

 

3-2. JWT 생성 함수 적용

작성한 함수를 index.js에 다음과 같이 추가해준다.

// index.js

app.post("/api/users/login", (req, res) => {
  console.log("/api/users/login");

  // 요청된 이메일을 DB에서 존재하는지 확인
  User.findOne({ email: req.body.email })
    .then((user) => {
      if( !user ) {
        return res.json({
          loginSuccess: false,
          message: "해당 이메일 정보가 없습니다.",
        });
      }

      // 요청된 이메일이 DB에 있다면 비밀번호가 일치하는지 확인
      user.comparePassword(req.body.password, (err, isMatch) => {
        if(!isMatch) {
          return res.json({
            loginSuccess: false,
            message: "비밀번호가 일치하지 않습니다.",
          });
        }
  
      // 비밀번호까지 맞다면 토큰 생성
      user.generateToken((err, user) => {
        if(err) {
          return res.status(400).send(err);
        }

        console.log(user);
      });
    });
  });
});

 

포스트맨으로 email과 password를 전달해서 로그인 테스트를 진행해본다.

 

로그인 성공 시 다음과 같이 DB에 JWT token이 잘 저장된 것을 확인할 수 있다.

그림 3-1. 로그인 성공 시 JWT 토큰 저장

 

 

4. JWT 토큰 저장

로그인 정보를 저장할 때 세션, 쿠키, 스토리지를 사용할 수 있다.

 

각 저장소마다 특징과 장단점이 존재하지만 간단히 설명하면 다음과 같다.


Local Storage

 • 브라우저를 종료해도 명시적으로 삭제하지 않는 이상 영구적으로 저장된다.

 

Session

 • 브라우저가 서버에 연결돼있는 동안 유지하는 데이터 집합이다.

 • 메모리에 존재하기 때문에 브라우저가 종료되면 데이터가 삭제된다.

 

Cookie

 • 클라이언트가 서버에 방문한 정보를 클라이언트단에 저장하는 작은 파일이다.

 • 클라이언트의 브라우저 메모리 또는 하드디스크에 저장된다.

 • 데이터의 유효시간을 설정할 수 있고, 대부분의 브라우저가 지원한다.


 

더욱 자세한 내용은 아래 블로그를 참고하면 된다.

https://joyhong-91.tistory.com/51

 

쿠키, 세션, 웹 스토리지 차이 (cookie, session, web storage)

1. 쿠키, 웹스토리지 (로컬스토리지, 세션스토리지) 생성 배경 HTTP는 요청과 응답을 주고 받아 한 사이클이 종료되면 연결이 끊어지는 무상태성을 가지고 있기 때문에 클라이언트의 상태를 보존

joyhong-91.tistory.com

 

이번 프로젝트에선 cookie에 JWT 토큰을 저장하도록 하겠다.

 

쿠키를 사용하기 위해 라이브러리를 설치해준다.

npm install cookie-parser

 

 

4-1. cookie-parser

cookie-parser는 요청된 쿠키를 쉽게 추출할 수 있도록 도와주는 미들웨어이다.

 

express의 request 객체에 cookie속성이 부여될 수 있게 index.js안에 다음 코드를 추가해준다.

// index.js
import cookieParser from "cookie-parser";

...

// application/x-www-form/urlencoded
app.use(bodyParser.urlencoded({extended: true}));

// application/json
app.use(bodyParser.json());
app.use(cookieParser());

...

 

 

4-2. 로그인 성공 시 토큰 값 쿠키 저장

index.js의 로그인 함수에 JWT 토큰을 쿠키에 저장해주는 코드를 추가해준다.

 

회원 로그인 전체 코드는 다음과 같다.

app.post("/api/users/login", (req, res) => {
  console.log("/api/users/login");

  // 요청된 이메일을 DB에서 존재하는지 확인
  User.findOne({ email: req.body.email })
    .then((user) => {
      if( !user ) {
        return res.json({
          loginSuccess: false,
          message: "해당 이메일 정보가 없습니다.",
        });
      }

      // 요청된 이메일이 DB에 있다면 비밀번호가 일치하는지 확인
      user.comparePassword(req.body.password, (err, isMatch) => {
        if(!isMatch) {
          return res.json({
            loginSuccess: false,
            message: "비밀번호가 일치하지 않습니다.",
          });
        }
  
      // 비밀번호까지 맞다면 토큰 생성
      user.generateToken((err, user) => {
        if(err) {
          return res.status(400).send(err);
        }

        // 토큰 저장 -> 쿠키
        res.cookie("x_auth", user.token)
          .status(200)
          .json({
            loginSuccess: true,
            userId: user._id,
          });
      });
    });
  });
});

 

 

로그인 테스트

포스트맨으로 로그인 정보를 넣고 해당 API로 전송 성공 화면은 아래와 같다.

그림 4-1. 로그인 성공 시 쿠키에 JWT 토큰 저장

 

 

 

 

 

 

 

 

 

 

728x90

댓글