[개발심화] token - 7주차 (4)

Hong·2022년 10월 29일
0




token은 왜 필요할까

sessionuser가 정보를 요청할 때마다 데이터 베이스를 뒤져가며 user가 보낸 session id와 일치하는 user의 id, password조합이 있는지 찾아야한다. 이것은 꽤나 불편한 일이고 부담스러운 일이다.
이러한 불편을 해소하기 위해 token을 사용한다.


token이란 무엇인가

[세션 vs 토큰 vs 쿠키? 기초개념 잡아드림. 10분 순삭!]

https://www.youtube.com/watch?v=tosLBcAX1vk

간단하게 말하면 session과 달리 server입장에서 user가 요청을 보낼 때 매번 DB를 뒤질 필요 없이 로그인 인증을 가능하게 해주는 녀석이다

대표적인 녀석으로 JWT token이 존재하고 두 가지 토큰을 사용한다.
1.Access Token : 실제로 권한을 얻는데 사용됨
2.Refresh Token : token정보가 만료되거나 탈취되었을 경우 새로운 access token을 발급받을 수 있게 해준다




👾 코드를 통해 살펴보자

access token을 가지고 있는 client가 서버로 유저정보를 요청할 때 처리해주는 모듈이다.

const { Users } = require('../../models');
const jwt = require('jsonwebtoken');
require("dotenv").config() //이거는 뭐냐..? -> 환경변수 env파일 정보 들고오는 라이브러리인듯
// sign 은 토큰화, 본문 => 암호문
// verify 토큰화를 본문, 암호문 => 본문

module.exports = (req, res) => {

  // <access token을 가지고 있는 client가 서버로 유저정보를 요청할 때 처리해주는 모듈이다.>
  // Authorization header에 담은 access token이 유효한지 확인한다 즉, 서버가 가지고 있는 비밀 키로 이전에 생성한 토큰이 맞는지 확인한다.
  // 이 경우에도 Authorization header에 유효한 토큰이 있는지 없는지를 분기를 만들어 작성한다.
  // 만약 Aturhorization header에 정상적인 토큰이 있다면, 토큰을 해독해서 얻은 payload 즉 데이터 유저 정보를(필요한 정보 : id, userId, email, createdAt, updatedAt)를 반환한다.
  // 만약 Aturhorization header에 토큰 자체가 없거나, 서버가 들고 있는 비밀 키로 해독할 수 없는 경우 {"data" : null, "message" : "invalid access token"}


  let authorization = req.headers.authorization; //client가 request한 데이터의 headers에서 authorization을 들고온다.


  if(authorization) {
    // <클라이언트가 보낸 요청에 인증서가 존재하는 경우>

    authorization = authorization.substr(7); //모든 access token에는 Bearer가 붙기 때문에 substr으로 잘라줬다(다른 방법으로 잘라줘도 상관은 없음)
    
    const draft = jwt.verify(authorization, process.env.ACCESS_SECRET);
    //근데 만약 이 해독한 본문이 정상적일 경우와 정상적이지 않을 경우를 나눠서 생각해본다
    //***jwt.veryfy(해독하고 싶은 token을 넣어준다, token을 해독할 수 있는 key값을 넣어준다, [options])

    if(draft) {
      // <클라이언트에서 보낸 request에 인증서가 있고 verify로 해독했을 때 정상적인 데이터가 있는 경우(ACCESS_SECRET으로 풀었을 때 정상적으로 풀리는 경우)>

      const requiredData = { //client에게 responsefh 돌려줘야 하는 데이터
        "id" : draft.id, 
        "userId" : draft.userId, 
        "email" : draft.email, 
        "createdAt" : draft.createdAt, 
        "updatedAt" : draft.updatedAt
      }
      
      return res.json({data:{userInfo: requiredData}, "message" :"ok"}).end() //정상 메세지와 client에게 데이터를 전달해준다

    } else {
      // <클라이언트에서 보낸 request에 인증서는 있지만 verify로 해독했을 때 정상적인 데이터가 없는 경우(ACCESS_SECRET으로 풀었을 때 정상적으로 풀리지 않는 경우)>

      return res.json({"data" : null, "message" :"invalid access token"}).end() //에러 메세지를 보내준다
    }

  } else {
    // <클라이언트가 보낸 요청에 인증서가 존재하지 않는 경우>
    return res.json({"data" : null, "message" :"invalid access token"}).end() //에러 메세지를 보내준다
  }
};

로그인 요청에 응답하는 프로세스를 만들어주는 곳이다

const { Users } = require("../../models");
const jwt = require('jsonwebtoken');

module.exports = async (req, res) => {
    // <로그인 요청에 응답하는 프로세스를 만들어주는 곳이다>
    //로그인에 필요한 정보 req.body.userId, req.body.password를 받아왔다.

    //client가 요청을 보냈을 때 로그인 정보가 있는 경우와 (요청한 데이터 중에 database에 일치하는 것이 있다면 id, userId, email, createdAt, updatedAt을 token의 payload에 담아 JWT token을 생성한다)
    //그리고 만든 refresh token을 res.cookie에 담아주자(만약 서버에서 처음 클라이언트에게 쿠키를 보낸다면 Set-Cookie를 사용해서 넘겨주자)
    //여기까지 성공하면 {"data" : {"accessToken" : 생성한_access_token}, "message": "ok"}

    //client가 요청을 보냈을 때 로그인 정보가 없는 경우를 구분해서 만들어주자(요청한 데이터 중에 database에 일치하는 것이 없다면 {"data" : null, "message" : "not authorized"}를 반환하자)
    

    // TODO: urclass의 가이드를 참고하여 POST /login 구현에 필요한 로직을 작성하세요.
    // HINT: auth-session과 마찬가지로, sequelize를 사용해 DB를 조회하세요. 이 조회를 통해서 client가 요청한 userId, password와 일치하는 데이터가 DB에 존재하는지 파악한다.
    


    const data = await Users.findOne({
        where: { userId: req.body.userId, password: req.body.password },
    });

        // console.log(data)를 찍어서 data가 어떻게 생겼는지 보고 필요한 값들을 들고온다
        // let id = data.id
        // let userId = data.userId
        // let email = data.email
        // let createdAt = data.createdAt
        // let updatedAt = data.updatedAt
        //json값 들고오는 법 공부
        
        
        if(!data) { 
            // <req.body에 userId와 password가 제대로 저장되어 있지 않은 경우>
            
            res.status(404).json({"data" : null, "message" : "not authorized"}).end() //에러 메세지를 보내준다
        } else {
            // <req.body에 userId와 password가 제대로 저장되어 있는 경우>
        
            const requiredData = { //token을 생성할 때 header가 되는 부분이다
                "id" : data.id, 
                "userId" : data.userId, 
                "email" : data.email, 
                "createdAt" : data.createdAt, 
                "updatedAt" : data.updatedAt
            }

            const accesstoken = jwt.sign(requiredData, process.env.ACCESS_SECRET, {expiresIn: "30m"}); //token의 signitur부분은 {algorithm(어떤 방식으로 암호화 할건지 결정함), issuer(토큰발행자 토큰 발행하는 client의 이름을 넣어주면 됨), expireIn(만료일자를 적어줌)}로 구성된다
            const refreshtoken = jwt.sign(requiredData, process.env.REFRESH_SECRET, {expiresIn: "30m"})
            

        res.cookie("refreshToken", refreshtoken) // cookie에 refreshtoken을 넣어준다
        res.status(200).json({"data" : {"accessToken" : accesstoken}, "message" : "ok"}).end() //client에게 ok메세지와 함께 accesstoken을 보내준다
    }
};

access token이 만료되서 refresh token으로 새로운 access token을 발급받고, client가 요청한 정보를 반환하는 모듈이다.

const { Users } = require('../../models');
const {verify, sign} = require("jsonwebtoken");
require("dotenv").config();

module.exports = (req, res) => {
  //access token이 만료되서 refresh token으로 새로운 access token을 발급받고, client가 요청한 정보를 반환하는 모듈이다.
  //여기서는 client의 cookie에 refresh token이 존재하는지 안하는지 여부에 따라 분기를 나눠서 작성해주자
  //여기서 우선 client의 cookie에 refresh token이 존재하는지 확인한다. 존재하지 않는다면 {"data" : null, "message" : "refresh token not provided"}
  
  //만약 client의 cookiedp refresh token이 존재하지만 비밀 키로 해독이 가능한지 안한지 여부에 따라 분기를 나눠 작성해준다.
  //비밀 키로 해독이 가능한 것이 확인되지 않는다면 {"data" : null, "message" : "invalid refresh token, please log in again"}을 반환한다.
  //비밀 키로 해독이 가능한 것이 확인된다면 필요한 정보들(id, userId, email, createdAt, updatedAt을 payload)에 담아 JWT token을 생성한다


  const isRefreshToken = req.cookies.refreshToken;
  //console.log(isRefreshToken)와 textcode를 통해 req.cookies.refreshToken가 3가지 상태를 가지고 있음을 알 수 있다.
  //undefined : refreshToken이 존재하지 않음
  //invalidtoken : refreshToken이 존재하지만 만료됨
  //dalsjfblajsfblejnfla : 이렇게 생긴 이상한 문자열, refreshToken이 정상적으로 존재함


  if(isRefreshToken) {
    // <refresh token이 정상적으로 존재하는 경우>
     
    if(isRefreshToken === 'invalidtoken') {
      // <refresh token이 존재하지만 invalidtoken으로써 존재하는 경우>
      return res.json({data : null, message : "invalid refresh token, please log in again"}).end() //토큰을 재발급 받으라고 메세지를 보내준다
    }

    // <refresh token이 존재하고 정상적으로 있는 경우>
    const draft = verify(isRefreshToken, process.env.REFRESH_SECRET); //refresh token을 REFRESH_SECRET을 이용해 암호화를 풀어주고 정상인지 확인한다

    const requiredData = { //client가 request한 cookies안에 token을 해독해서 얻어낸 draft에서 얻어낸 데이터들 이것을 다시 ACCESS key로 암호화 해서 client에게 보내줄 거임
      "id" : draft.id, 
      "userId" : draft.userId, 
      "email" : draft.email, 
      "createdAt" : draft.createdAt, 
      "updatedAt" : draft.updatedAt
    }

    const accesstoken = sign(requiredData, process.env.ACCESS_SECRET, {expiresIn: "30m"}); //데이터를 다시 ACCESS key로 암호화 해준다
    
    return res.json({data : {userInfo: requiredData, accessToken : accesstoken}, "message" :"ok"}).end() //response로 client에게 전달해준다
    

  } else {
    // <refresh token이 정상적으로 존재하지 않는 경우>
    return res.json({data : null, message : "refresh token not provided"}).end() //client에게 refresh token이 존재하지 않는다고 메세지를 전달해준다
  }
};



언제쯤 주석이 없어질까🥲

profile
Notorious

0개의 댓글