댓글 검색 목록

[javascript] Passport-JWT를 사용하여 Node.js API를 설정하는 단계별 가이드

페이지 정보

작성자 운영자 작성일 20-08-31 11:06 조회 682 댓글 0

인증 및 권한 부여는 응용 프로그램의 큰 부분입니다. 보호 또는 검사가 없는 API 경로가 있을 때마다 애플리케이션은 쉽게 해커의 표적이 될 수 있습니다. 그렇기 때문에 보안 토큰, 즉 JWT (JSON Web Token)가 필요합니다.


https://dev.to/calvinqc/a-step-by-step-guide-to-setting-up-a-node-js-api-with-passport-jwt-5fa5


JWT의 기본 사항 


JWT에 너무 깊이 들어 가지 않겠지만 여기에 모든 기본 사항이 있습니다.


https://cdn-images-1.medium.com/max/800/0*a5Oo7gACg1vPfTMo.png 


JWT (JSON Web Token)는 당사자 간에 정보를 JSON 개체로 안전하게 전송하기 위한 컴팩트 하고 독립적 인 방법을 정의하는 개방형 표준 (RFC 7519)입니다. 이 정보는 디지털 서명이 되어 있으므로 확인하고 신뢰할 수 있습니다. JWT는 비밀 (HMAC 알고리즘 사용) 또는 RSA 또는 ECDSA를 사용하는 공개 / 개인 키 쌍을 사용하여 서명 할 수 있습니다. 


JSON 웹 토큰은 사용자 정보를 인코딩하고 디코딩 합니다. 인증 및 정보 교환에 사용됩니다.


다음과 같이 점 (.)으로 구분 된 세 부분 (헤더, 페이로드 및 서명)으로 구성됩니다. xxxxx.yyyyy.zzzzz


여기에서 JSON 웹 토큰에 대해 자세히 알아보세요.


시작하기 전에 


비디오를 확인하지 않으면 컴퓨터에 이미 npm이 있고 Postman으로 테스트한다고 가정합니다.


절차에 문제가 있는 경우 코드가 있고 질문이 필요한 경우 Trivin의 Slack에 가입하세요.


서버 설정 


자체 서버를 사용하려면 이 단계를 건너 뛰십시오.


프로젝트가 없는 경우 Trivin을 사용하여 프로젝트 템플릿을 설정합니다. 이 기사에서는 이를 사용하여 단순 노드 서버를 만듭니다.


$ npm i trivin -g
$ trivin server simple-node-server -g -i



이렇게 하면 간단하지만 잘 구성된 노드 서버가 생성되고 Git을 초기화하고 모든 프로젝트 종속성이 설치됩니다.


설치 


$ npm i passport passport-jwt winston cors express-validator jsonwebtoken



지원 파일 설정 


$ mkdir store/ 
$ touch store/passport.js store/config.js store/utils.js controller/constant.js


Constant.js 


  • 첫째, constant.js 파일에서 제가 정말하고 싶은 일이 있습니다. 많은 문자열을 작성하는 대신 재사용 할 가능성이 있는 문자열에 대한 변수를 만듭니다.
  • TextEditor가 자동 완성되도록 허용하고 문자열의 오타를 줄이십시오.
  • constant.js 파일에 다음을 추가하십시오.
export const EMAIL_IS_EMPTY = 'EMAIL_IS_EMPTY';
export const PASSWORD_IS_EMPTY = 'PASSWORD_IS_EMPTY';
export const PASSWORD_LENGTH_MUST_BE_MORE_THAN_8 =
  'PASSWORD_LENGTH_MUST_BE_MORE_THAN_8';
export const WRONG_PASSWORD = 'WRONG_PASSWORD';
export const SOME_THING_WENT_WRONG = 'SOME_THING_WENT_WRONG';
export const USER_EXISTS_ALREADY = 'USER_EXISTS_ALREADY';
export const USER_DOES_NOT_EXIST = 'USER_DOES_NOT_EXIST';
export const TOKEN_IS_EMPTY = 'TOKEN_IS_EMPTY';
export const EMAIL_IS_IN_WRONG_FORMAT = 'EMAIL_IS_IN_WRONG_FORMAT';



utils.js 


  • 프로젝트 전체에서 사용되는 모든 기능과 유효성 검사를 저장하는 파일입니다.
  • API 컨트롤러 파일의 코드가 훨씬 깔끔해집니다.
import sha256 from 'sha256';
import { check } from 'express-validator';
import {
  PASSWORD_IS_EMPTY,
  PASSWORD_LENGTH_MUST_BE_MORE_THAN_8,
  EMAIL_IS_EMPTY,
  EMAIL_IS_IN_WRONG_FORMAT,
} from './constant';
export const generateHashedPassword = password => sha256(password);
export function generateServerErrorCode(res, code, fullError, msg, location = 'server') {
  const errors = {};
  errors[location] = {
    fullError,
    msg,
  };
return res.status(code).json({
    code,
    fullError,
    errors,
  });
}
// ================================
// Validation:
// Handle all validation check for the server
// ================================
export const registerValidation = [
  check('email')
    .exists()
    .withMessage(EMAIL_IS_EMPTY)
    .isEmail()
    .withMessage(EMAIL_IS_IN_WRONG_FORMAT),
  check('password')
    .exists()
    .withMessage(PASSWORD_IS_EMPTY)
    .isLength({ min: 8 })
    .withMessage(PASSWORD_LENGTH_MUST_BE_MORE_THAN_8),
];
export const loginValidation = [
  check('email')
    .exists()
    .withMessage(EMAIL_IS_EMPTY)
    .isEmail()
    .withMessage(EMAIL_IS_IN_WRONG_FORMAT),
  check('password')
    .exists()
    .withMessage(PASSWORD_IS_EMPTY)
    .isLength({ min: 8 })
    .withMessage(PASSWORD_LENGTH_MUST_BE_MORE_THAN_8),
];


Passport.js 설정 


  • 인증에 도움이 되는 node.js 라이브러리.
  • store / passport.js에 다음을 추가하십시오.
import { Strategy, ExtractJwt } from 'passport-jwt';
import { config, underscoreId } from './config';
import { User } from '../database/models';

export const applyPassportStrategy = passport => {
  const options = {};
  options.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
  options.secretOrKey = config.passport.secret;
  passport.use(
    new Strategy(options, (payload, done) => {
      User.findOne({ email: payload.email }, (err, user) => {
        if (err) return done(err, false);
        if (user) {
          return done(null, {
            email: user.email,
            _id: user[underscoreId]
          });
        }
        return done(null, false);
      });
    })
  );
};


  • store / config.js는 앱의 모든 구성을 보관하는 곳입니다.
export const config = {
  passport: {
    secret: '<Add_Your_Own_Secret_Key>',
    expiresIn: 10000,
  },
  env: {
    port: 8080,
    mongoDBUri: 'mongodb://localhost/test',
    mongoHostName: process.env.ENV === 'prod' ? 'mongodbAtlas' : 'localhost',
  },
};
export const underscoreId = '_id';


passport과 함께 사용하도록 app.js를 수정합니다.


import express from 'express';
import logger from 'winston';
import bodyParser from 'body-parser';
import cors from 'cors';

import passport from 'passport';
import mongoose from 'mongoose';

import { config } from './store/config';
import { applyPassportStrategy } from './store/passport';
import { userController } from './controller';

const app = express();

// Set up CORS
app.use(cors());

// Apply strategy to passport
applyPassportStrategy(passport);
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

// API Route
app.use('/', userController);

/**
 * Get port from environment and store in Express.
 */
const { port, mongoDBUri, mongoHostName } = config.env;
app.listen(port, () => {
  logger.info(`Started successfully server at port ${port}`);
  mongoose
    .connect(mongoDBUri, { useNewUrlParser: true, useUnifiedTopology: true })
    .then(() => {
      logger.info(`Conneted to mongoDB at ${mongoHostName}`);
    });
});


앱 실행 


$ npm start


이제 다시 돌아가서 우리 API에 passport-jwt를 적용하여 user.controller.js를 개선해 보겠습니다.


등록 / 로그인 API에 Passport-jwt 적용 


https://cdn-images-1.medium.com/max/800/0*1d_RhWhLZJvy0WFk.png 



import express from 'express';
import jwt from 'jsonwebtoken';

import { validationResult } from 'express-validator';
import { config } from '../store/config';

import {
  generateHashedPassword,
  generateServerErrorCode,
  registerValidation,
  loginValidation,
} from '../store/utils';

import {
  SOME_THING_WENT_WRONG,
  USER_EXISTS_ALREADY,
  WRONG_PASSWORD,
  USER_DOES_NOT_EXIST,
} from '../store/constant';

import { User } from '../database/models';

const userController = express.Router();

const createUser = (email, password) => {
  const data = {
    email,
    hashedPassword: generateHashedPassword(password),
  };
  return new User(data).save();
}

/**
 * GET/
 * retrieve and display all Users in the User Model
 */
userController.get('/', (req, res) => {
  User.find({}, (err, result) => {
    res.status(200).json({ data: result });
  });
});

/**
 * POST/
 * Register a user
 */
userController.post('/register', registerValidation, async (req, res) => {
  const errorsAfterValidation = validationResult(req);
  if (!errorsAfterValidation.isEmpty()) {
    return res.status(400).json({
      code: 400,
      errors: errorsAfterValidation.mapped(),
    });
  } 
    try {
      const { email, password } = req.body;
      const user = await User.findOne({ email });

      if (!user) {
        await createUser(email, password);

        // Sign token
        const newUser = await User.findOne({ email });
        const token = jwt.sign({ email }, config.passport.secret, {
          expiresIn: 10000000,
        });
        const userToReturn = { ...newUser.toJSON(), ...{ token } };

        delete userToReturn.hashedPassword;

        res.status(200).json(userToReturn);
      } else {
        generateServerErrorCode(res, 403, 'register email error', USER_EXISTS_ALREADY, 'email');
      }
    } catch (e) {
      generateServerErrorCode(res, 500, e, SOME_THING_WENT_WRONG);
    }
});
/**
 * POST/
 * Login a user
 */
userController.post('/login', loginValidation, (req, res) => {
  const errorsAfterValidation = validationResult(req);
  if (!errorsAfterValidation.isEmpty()) {
    return res.status(400).json({
      code: 400,
      errors: errorsAfterValidation.mapped(),
    });
  } 
      const { email, password } = req.body;
      const user = await User.findOne({ email });
      if (user && user.email) {
        const isPasswordMatched = user.comparePassword(password);
        if (isPasswordMatched) {
          // Sign token
          const token = jwt.sign({ email }, config.passport.secret,         
          {
            expiresIn: 1000000,
          });
          const userToReturn = { ...user.toJSON(), ...{ token } };
          delete userToReturn.hashedPassword;
          res.status(200).json(userToReturn);
        } else {
          generateServerErrorCode(res, 403, 'login password error', WRONG_PASSWORD, 'password');
        }
      } else {
        generateServerErrorCode(res, 404, 'login email error', USER_DOES_NOT_EXIST, 'email');
      }
});
export default userController;


  • 인증을 위해 사용자의 이메일과 해시 된 비밀번호를 사용하는 대신 클라이언트와 서버 간의 통신 중에 보안이 유지되지 않을 수 있습니다.
  • 인증을 위해 JWT 토큰을 사용합니다. 이렇게 하면 암호와 사용자의 이메일을 암호화하여 보안을 유지할 수 있습니다.

테스팅 


  • 이 시점에서 Postman을 사용하는 방법을 알고 있다고 가정합니다.
  • POST / 메소드를 사용하고 localhost : 8080 / register 및 localhost : 8080 / login을 입력합니다.
  • Register API를 테스트하면 아래와 비슷한 결과를 얻을 수 있습니다. 토큰을 클립 보드에 복사합니다.

https://cdn-images-1.medium.com/max/800/1*EuWQ1Mo_cJwQzsI_V2rSRQ.png 


API 등록 성공 토큰 및 사용자 이메일 + ID 반환


권한 부여 


사용자가 로그인해야 하는 특정 링크로 이동 하려는지 살펴 보겠습니다. 그런 다음 API에 인증을 추가하기 만하면 됩니다.

예를 살펴 보겠습니다.


  • user.controller.js에는 모든 사용자의 목록을 검색하는 간단한 '/'API가 포함되어 있습니다 — 사용자로 로그인하지 않는 한 모든 사용자를 검색하고 싶지 않습니다.
  • 또 다른 예는 Facebook입니다. 뉴스 피드로 이동하여 모든 게시물을 검색하려면 로그인해야 합니다.
  • 다음은 JWT 토큰이 없는 보안 API 경로로 이동하는 경우의 예입니다 (즉, 로그인하지 않은 경우).

https://cdn-images-1.medium.com/max/800/1*Oft6XR83WafE79nMBVEjUA.png 

API에 연결된 JWT가없는 예


Passport JWT를 통한 승인 


user.controller.js에 다음 하이라이트를 추가하세요.


import express from 'express';
import jwt from 'jsonwebtoken';
import passport from 'passport';
import { validationResult } from 'express-validator';
...
/**
 * GET/
 * retrieve and display all Users in the User Model
 */
userController.get(
  '/',
  **passport.authenticate('jwt', { session: false }),**
  (req, res) => {
    User.find({}, (err, result) => {
      res.status(200).json({ data: result });
    });
  }
);
...
export default userController;


이제 Postman으로 API를 테스트하십시오. "Authorization"을 클릭하고 "Bearer Token"유형을 선택합니다. 그런 다음 토큰 필드에 토큰을 붙여 넣고 다음을 실행하십시오.


https://cdn-images-1.medium.com/max/800/1*y5u1ccu31puCLog8TPeOug.png 


JWT를 사용하면 모든 사용자를 검색 할 수 있습니다.


이제 API를 사용하기 전에 사용자가 로그인해야 하는 다른 모든 경로를 승인하고 보호 할 수 있습니다.



댓글목록 0

등록된 댓글이 없습니다.

웹학교 로고

온라인 코딩학교

코리아뉴스 2001 - , All right reserved.