23.05.07 웹개발_Backend (Token 의사코드)

Yeondong Choe·2023년 5월 7일
0
post-thumbnail

accessToken과 refreshToken의 흐름

[Server_tokenFunctions.js / 흐름0: 토큰 생성과 인증이 어떻게 만들어지는지 확인]

require('dotenv').config();
//환경변수를 가져와서 솔트값으로 사용 
const { sign, verify } = require('jsonwebtoken');
//jwt에서 사용할 수 있는 모듈인 sign, verify 함수를 받아옴
//sign은 토큰을 만드는 함수, verify는 토큰을 검증하는 함수
module.exports = {
  generateToken: (user, checkedKeepLogin) => {
    //user의 정보를 받아와서 payload를 만들어줌
    const payload = {
      id: user.id,
      email: user.email,
    };
    let result = {
      //accessToken이라는 키값안에 sign을 사용해서 토큰을 만들고 그 안에 payload와 환경변수로 관리중인 ACCESS 솔트값을 가져오고 있음
      accessToken: sign(payload, process.env.ACCESS_SECRET, {
        //옵션으로 유효기간을 설정해줌
        expiresIn: '1d', // 1일간 유효한 토큰을 발행합니다.
      }),
    };
    //로그인 유지 여부에 따라 처리해주고 있음
    if (checkedKeepLogin) {
      //refreshToken이라는 키값안에 sign을 사용해서 토큰을 만들고 그 안에 payload와 환경변수로  관리중인 REFRESH 솔트값을 가져오고 있음
      result.refreshToken = sign(payload, process.env.REFRESH_SECRET, {
        //옵션으로 유효기간을 설정해줌
        expiresIn: '7d', // 일주일간 유효한 토큰을 발행합니다.
      });
    }
    //로그인 유지 여부가 체크되어있으면 accessToken , refreshToken 둘다 들어있는 토큰이 만들어짐
    //로그인 유지 여부가 체크되어있지 않으면 accessToken만 들어있는 토큰이 만들어짐
    return result;
  },
  verifyToken: (type, token) => {
    //type으로 case의 여부를 확인하는 인자와 token을 인자로 받아옴
    let secretKey, decoded;
    switch (type) {
      case 'access':
        //환경변수로 관리중인 솔트값을 사용하고 있음
        secretKey = process.env.ACCESS_SECRET;
        break;
      case 'refresh':
        //환경변수로 관리중인 솔트값을 사용하고 있음
        secretKey = process.env.REFRESH_SECRET;
        break;
      default:
        return null;
    }

    try {

      //token 안에는 header와 payload가 있기때문에 솔트값만 알 수 있으면 검증할 수 있음
      //그래서 사용된 secretKey를 받아와서 검증하려고 함.
      decoded = verify(token, secretKey);
    } catch (err) {
      console.log(`JWT Error: ${err.message}`);
      return null;
    }
    return decoded;
  },
};
[Server_login.js / 흐름1: 들어오는 정보에 맞게 토큰을 생성해주고 쿠키로 전달하기]

const { USER_DATA } = require('../../db/data');
// JWT는 generateToken으로 생성할 수 있습니다. 먼저 tokenFunctions에 작성된 여러 메서드들의 역할을 파악하세요.
const { generateToken } = require('../helper/tokenFunctions');

module.exports = (req, res) => {
  //로그인정보가 잘들어오는지 확인해보기
  console.log(req.body)
  const { userId, password } = req.body.loginInfo;
  const { checkedKeepLogin } = req.body;
  // checkedKeepLogin이 false라면 Access Token만 보내야합니다.
  // checkedKeepLogin이 true라면 Access Token과 Refresh Token을 함께 보내야합니다.
  const userInfo = {
    ...USER_DATA.filter((user) => user.userId === userId && user.password === password)[0],
  };

  //가지고 있는 정보와 들어온 유저정보가 같은지 확인하고 아니면 권한 없음
  if(userInfo.id === undefined) {
    res.status(401).send('Not Authorized')
  } 
  //정보가 일치한다면 generateToken에 userInfo, checkedKeepLogin를 넣어주기
  else {

    const cookieOptions = {
      //어떤 도메인에 저장할것인지 정해주기
      domain: 'localhost',
      path: '/',
      //https 프로토콜을 사용할때만 쿠키를 전달해준다는 옵션(localhost는 개발용이기에 안적어주는 경우도 많음, 탈취의 위험이 없기때문, 사용하게된다면 항상 true로 하기)
      secure: true,
      //쿠키는 consloe창에서 확인을 할 수 있는데 httpOnly 옵션을 true로 주면 console에서 쿠키 확인을 막을 수 있음 보안을 위해 항상 true로 주기
      httpOnly: true,
      //프로토콜과 포트가 가르고 사이트가 같은곳만 전달하는 옵션
      sameSite: 'strict'
      //expir, maxAge 설정이 없기때문에 브라우저를 닫으면 쿠키가 없어짐
    }
    
    const {accessToken, refreshToken } = generateToken(userInfo, checkedKeepLogin)
    //토큰들이 잘 들어오는지 확인하기
    console.log(accessToken)
    console.log(refreshToken)
    //쿠키를 만들어주고 쿠키이름, 저장할 값, 저장할때 옵션
    res.cookie('access_jwt', accessToken, cookieOptions)

    //로그인 상태 유지가 체크되어있다면
    if(checkedKeepLogin) {
      //쿠키를 만들어주고 쿠키이름, 저장할 값, 저장할때 옵션
      res.cookie('refresh_jwt', refreshToken, cookieOptions)
      cookieOptions.maxAge = 1000 * 60 * 60 * 24 * 7
    }
    res.redirect('/userinfo')
  }
};

로그인 정보가 들어오는 부분과 accessToken, refreshToken을 콘솔로 확인

//흐름2: login.js로 부터 토큰을 전달 받았다면 토큰의 유효여부에 따라 유저의 정보를 보내주기

const { USER_DATA } = require('../../db/data');
// JWT는 verifyToken으로 검증할 수 있습니다. 먼저 tokenFunctions에 작성된 여러 메서드들의 역할을 파악하세요.
const { verifyToken, generateToken } = require('../helper/tokenFunctions');

module.exports = (req, res) => {
  //cookies를 잘받아오고있는지 확인해보기
  console.log(req.cookies)
  //구조분해할당으로 받아오고 있는 쿠키의 토큰 가져오기
  const {access_jwt, refresh_jwt} = req.cookies
  
  //access_jwt을 받아오고 있다면 검증을 해줄텐데, verifyToken의 인자로 type과 token을 받아오고 있기때문에 그에 맞게 넣어주기
  //if(access_jwt) {
  //console.log(verifyToken('access', access_jwt))
  //console.log로 확인해보면 넣었던 payload를 가져오는것을 알 수 있음
  //}

    //따라서 access_jwt일때 검증해서 가져오는 payload를 변수로 저장해서 사용
    const accessPayload = verifyToken('access', access_jwt)
    const refreshPayload = verifyToken('refresh', refresh_jwt)
    //값이 맞다면 저장된 유저정보와 들어온 유저정보와 같은값의 정보를 보내줌  
    if (accessPayload) {
      const userInfo = {
        ...USER_DATA.filter((user) => user.id === accessPayload.id)[0],
      };
      //유저정보가 제대로 들어오지 않은 경우
      if(!userInfo.id) {
        return res.status(401).send('Not Authorized')
      }
      delete userInfo.password
      return res.send(userInfo)
    } 
    //access_jwt이 제대로 들어오지 않았다면 refresh_jwt여부를 확인하기
    else if(refreshPayload) {
    //값이 맞다면 저장된 유저정보와 들어온 유저정보와 같은값의 정보를 보내줌  
      const userInfo = {
        ...USER_DATA.filter((user) => user.id === refreshPayload.id)[0],
      };
      //유저정보가 제대로 들어오지 않은 경우
      if(!userInfo.id) {
        return res.status(401).send('Not Authorized')
      }
      //값이 잘 들어왔다면 accessToken을 만들어주기
      const {accessToken} = generateToken(userInfo)
      const cookieOptions = {
        domain: 'localhost',
        path: '/',
        secure: true,
        httpOnly: true,
        sameSite: 'strict'
      }
      res.cookie('access_jwt', accessToken, cookieOptions)
      return res.redirect('/userinfo')
    } 
    //refresh_jwt가 없다면
    else {
      return res.status(401).send('Not Authorized')
  }
};

cookies로 어떻게 들어오는지 확인해보면 access_jwt, refresh_jwt이 들어오는것을 알 수 있다.

access_jwt을 받아올때 (verifyToken('access', access_jwt))로 검증을 해본다면 콘솔에 payload를 가져오는것을 알 수 있음

//흐름3: 로그아웃 구현

module.exports = (req, res) => {
  //로그아웃할때는 쿠키를 삭제해주면 됨 쿠키를 삭제할때는 만들었던 쿠키의 옵션을 그대로 적어야함
  const cookieOptions = {
    domain: 'localhost',
    path: '/',
    secure: true,
    httpOnly: true,
    sameSite: 'strict'
  }
  //clearCookie로 삭제할 수 있으며 삭제해줄 토큰 이름과 옵션을 적어주기
  res.clearCookie('access_jwt', cookieOptions)
  res.clearCookie('refresh_jwt', cookieOptions)
  return res.status(205).send('logout')
};
profile
개발자 동동

0개의 댓글