티스토리 뷰

토큰 관리

토큰 정책

토큰 발행 정책

  • 토큰 발행 시 만료 시간은 2시간으로 정한다.
  • 토큰의 payload에는 사용자의 pk, 이름, 아이디, 권한 정보를 넣는다.
  • 토큰 발행은 응답헤더의 token으로 한다.

토큰 검증 정책

  • 클라이언트에서 발송하는 토큰 정보는 요청헤더의 token으로 한다.
  • 토큰 검증이 확인 되면 매번 새로운 토큰을 갱신 발급해 준다.
  • 토큰 갱신은 응답헤더의 token으로 한다.

토큰 폐기 정책

  • 토큰 폐기시 로그아웃에 대한 후속 처리는 별도로 하지 않는다.

토큰 발행

아이디/비밀번호가 맞으면 토큰을 발행해 준다.

jsonwebtoken 설치

> npm install jsonwebtoken --save

토큰용 secretKey생성

토큰에서 사용할 secretKey를 생성해야 한다.
secretKey를 생성할때에는 키 생성 전용 서비스를 이용하는 것이 좋다.
https://www.allkeysgenerator.com/Random/Security-Encryption-Key-Generator.aspx

생성할 시크릿 키는 64 hex characters(=256 binary bits)로 만들어 준다.

토큰 처리 유틸 생성

토큰을 생성하고 확인해주는 유틸리티를 만들어 준다.
위에서 생성한 시크릿 키를 넣어 준다. (시크릿 키 절대 노출 금지)

/lib/tokenUtil.js

const jwt = require('jsonwebtoken');

const secretKey = '자신꺼';
const options = {
  expiresIn: '2h', // 만료시간
};

const tokenUtil = {
  // 토큰 생성
  makeToken(user) {
    const payload = {
      id: user.id,
      userid: user.userid,
      name: user.name,
      role: user.role,
    };

    const token = jwt.sign(payload, secretKey, options);

    return token;
  },
};

module.exports = tokenUtil;

로그인 처리용 프로세스

dao 처리

/dao/userDao.js

...(중간생략)...
  // 로그인을 위한 사용자 조회
  selectUser(params) {
    return new Promise((resolve, reject) => {
      User.findOne({
        attributes: ['id', 'userid', 'password', 'name', 'role'],
        where: { userid: params.userid },
      }).then((selectedOne) => {
        resolve(selectedOne);
      }).catch((err) => {
        reject(err);
      });
    });
  },
...(중간생략)...

service 처리

/user/userService.js

...(중간생략)...
  // login 프로세스
  async login(params) {
    // 1. 사용자 조회
    let user = null;
    try {
      user = await userDao.selectUser(params);
      logger.debug(`(userService.login) ${JSON.stringify(user)}`);

      // 해당 사용자가 없는 경우 튕겨냄
      if (!user) {
        const err = new Error('Incorect userid or password');
        logger.error(err.toString());

        return new Promise((resolve, reject) => {
          reject(err);
        });
      }
    } catch (err) {
      logger.error(`(userService.login) ${err.toString()}`);
      return new Promise((resolve, reject) => {
        reject(err);
      });
    }

    // 2. 비밀번호 비교
    try {
      const checkPassword = await hashUtil.checkPasswordHash(params.password, user.password);
      logger.debug(`(userService.checkPassword) ${checkPassword}`);

      // 비밀번호 틀린 경우 튕겨냄
      if (!checkPassword) {
        const err = new Error('Incorect userid or password');
        logger.error(err.toString());

        return new Promise((resolve, reject) => {
          reject(err);
        });
      }
    } catch (err) {
      logger.error(`(userService.checkPassword) ${err.toString()}`);
      return new Promise((resolve, reject) => {
        reject(err);
      });
    }

    return new Promise((resolve) => {
      resolve(user);
    });
  },
...(중간생략)...

router 처리

router는 auth로 새로 만들어 준다.

/routes/auth.js

const express = require('express');

const router = express.Router();
const logger = require('../lib/logger');
const tokenUtil = require('../lib/tokenUtil');
const userService = require('../service/userService');

// user 토큰 발행
router.post('/token', async (req, res) => {
  try {
    const params = {
      userid: req.body.userid,
      password: req.body.password,
    };
    logger.info(`(auth.token.params) ${JSON.stringify(params)}`);

    // 입력값 null 체크
    if (!params.userid || !params.password) {
      const err = new Error('Not allowed null (userid, password)');
      logger.error(err.toString());

      res.status(500).json({ err: err.toString() });
    }

    // 비즈니스 로직 호출
    const result = await userService.login(params);
    logger.info(`(auth.token.result) ${JSON.stringify(result)}`);

    // 토큰 생성
    const token = tokenUtil.makeToken(result);
    res.set('token', token); // header 세팅

    // 최종 응답
    res.status(200).json({ token });
  } catch (err) {
    res.status(500).json({ err: err.toString() });
  }
});

module.exports = router;

router index 등록

/auths라는 이름으로 라우터에 등록한다.

/routes/index.js

...(중간생략)...
const  userRouter = require('./user');
const  authRouter = require('./auth');

...(중간생략)...
router.use('/users', userRouter);
router.use('/auths', authRouter);
...(중간생략)...

토큰 검증(middleware)

백엔드의 API를 호출했을때 로그인 여부를 확인(토큰 검증)하여 로그인이 된 상태에서만 프로세스가 동작 하도록 하자.
(권한 체크도 가능하지만 여기서는 로그인 여부만 확인 한다.)

토큰검증 함수 생성

토큰 검증을 위한 함수를 생성 한다.

/lib/tokenUtil.js

const jwt = require('jsonwebtoken');
...(중간생략)...

const tokenUtil = {
  ...(중간생략)...
  // 토큰 검증
  verifyToken(token) {
    try {
      const decoded = jwt.verify(token, secretKey);

      return decoded;
    } catch (err) {
      return null;
    }
  },
};

module.exports = tokenUtil;

미들웨어(middleware) 함수 생성

위에서 생성한 토큰 검증 함수를 미들웨어를 통해 사용할 수 있다.
미들웨어 전용 함수를 만들어 보자.

/lib/middleware.js

const logger = require('./logger');
const tokenUtil = require('./tokenUtil');

const middleware = {
  // 로그인 체크
  isLoggedIn(req, res, next) {
    const token = req.headers && req.headers.token;

    if (token) {
      // 토큰이 있는 경우 토큰 검증을 수행 한다.
      const decoded = tokenUtil.verifyToken(token);

      if (decoded) {
        // 1. 토큰 검증이 성공한 경우 새로 갱신해 준다.
        const newToken = tokenUtil.makeToken(decoded);
        res.set('token', newToken); // header 세팅

        next(); // 미들웨어 통과(계속 진행)
      } else {
        // 2. 토큰 검증이 실패한 경우 401에러를 응답 한다.
        const err = new Error('Unauthorized token');
        logger.error(err.toString());

        res.status(401).json({ err: err.toString() });
      }
    } else {
      // 토큰이 없는 경우 401에러 응답
      const err = new Error('Unauthorized token');
      logger.error(err.toString());

      res.status(401).json({ err: err.toString() });
    }
  },
};

module.exports = middleware;

미들웨어는 프로세스 진행 중간에 체크할 사항이 있는 경우 위와 같이 함수를 만들어서 사용하면 편리하다.
미들웨어 사용방법에 대해서는 다음 항목인 장비관리에서 확인해 보도록 하자.

토큰 폐기

토큰 폐기에 대한 별도의 후속 처리는 없으므로 토큰 폐기에 대한 백엔드는 만들지 않아도 된다.
(프론트에서 자체 보유한 토큰을 폐기하면 됨)

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함