[엘리스 SW트랙 4기] 7주차 - Day 35: Express.js와 MongoDB로 웹서비스 만들기(3)

랸나·2023년 4월 15일
0
1차 프로젝트 팀이 구성됐고 다음주면 진짜 프로젝트 시작이다. 알고있는 것이 너무 없어서 팀원들에게 민폐가 될까 걱정이다.
그렇지만.. 당당하게 무식한 감자 컨셉을 잡고 최대한 도움 될 수 있도록 모르는 것들을 많이 물어봐야겠다. 
몰입 가득한 2주를 보내자! 화이팅.. 
+ 그리고 그만 징징거리자! 절망은 짧게하고 행동은 곧바로 실천하자. 성실하게 하루하루를 보내다보면 어느새 성장해있을 것이다. 

01. JWT 의 이해

JSON Web Token

  • 인증을 위한 정보를 특별한 저장소를 사용하지 않고 전자서명을 이용하여 확인하는 방법
  • header : 토큰의 타입(jwt : Web Token, 즉 웹에서 사용하기 위한 스펙이므로 웹에서 문제 없이 사용할 수 있는 문자열로만 구성된 base 64 인코딩 사용), 데이터 서명 방식
  • payload : 전달되는 데이터
  • signature : 헤더와 페이로드의 전자서명

JWT 와 보안

  • JWT의 payload는 단순히 정보를 base64 encode
    -> decode시 정보 노출됨
    -> 민감한 정보는 제외하고 토큰 생성 필수

  • 서버는 JWT를 생성할 떄 비공개키를 이용하여 서명함. payload를 조작할 경우 서명이 일치하지 않기 때문에 인증 실패

JWT 작동 방식

  1. 사용자 로그인
  2. 서버는 로그인된 유저 정보를 JWT로 생성하여 클라이언트에 전달
  3. 클라이언트는 전달받은 JWT를 이용하여 인증이 필요한 요청에 사용

JWT 사용 이유

  • session은 웹 브라우저의 통신 스펙
  • 모바일 앱 어플리케이션의 경우 이를 활용하기 부적함
  • JWT를 사용하면 어느 클라이언트에서나 동일한 방식의 사용자 인증 구현 가능

02. JWT + Cookie 사용하기

🍪 Cookie란?

  • 웹 서비스에서 사용하는 정보를 클라이언트에 저장하고, HTTP요청 시 이를 함께 전송하여, 클라이언트 정보를 서버에 전달하는 기술
  • 세션 : 클라이언트 정보를 서버 측 저장소에 저장하고 사용
  • 쿠키: 클라이언트 정보를 클라이언트 (브라우저)에 저장하고 사용
  • Session을 사용한 유저 로그인
    -> Cookie에 Session ID 저장
    -> Session Store 에서 유저 정보 가져오기
  • JWT 를 쿠키에 저장하는 경우
    -> JWT로 요청
    -> 서명 확인 후 유저 정보 사용 ( 데이터베이스 접근이 줄어서 효율적인 인증 가능)

JWT 로그인 구현하기

  1. 기존 세션으로 구현된 로그인을 비활성화
//app.use(session(...)); 비활성화
//app.use(passport.session()); 비활성화
  1. 로그인 로직에서 JWT 생성 후 쿠키로 전달
setUserToken = (res, user) => {
  const token = jwt.sign(user, secret); //jwt 웹토큰 패키지 메소드를 통해 유저정보를 secret변수를 통해서 사인함.
  res.cookie('token', token); // toekn이라는 이름의 token을 쿠키로 보내고 있음
---
router.post('/', passport.authenticate('local'), (req, res, next) => {
	setUserToken(res, req.user);
  
  res.redirect('/');
 // 클라이언트는 쿠키를 res로 받고 토큰을 브라우저에 저장하게 됨
}
  
  1. passport-jwt 패키지로 JWT 로그인 미들웨어 작성 및 사용
  • 요청된 JWT 토큰의 서명을 확인하고 인증하는 기능을 구현
const JwtStrategy = require('passport-jwt').Strategy;
const cookieExtractor = (req) => {
	const {token} = req.cookies;
  return token; // 쿠키에서 토큰을 받아오는 절차 
};
const opts = { // config
	secretOrKey : secret, //직접 키를 넣으면 안되고, 환경변수 값으로 넣어야함. 
  jwtFromRequest : cookieExtractor,
}
module.exports = new JwtStrategy(opts, (user, done) => { done(null, user) // new JwtStrategy(config, callback) 구조 
)};
---
passport.use(jwt) 
                               

Jwt 미들웨어 추가

  • Jwt 토큰은 기본적으로 모든 요청에 포함
  • 요청에 토큰이 있는 경우 로그인된 상태로 처리하기 위해 모든 요청에 공통적으로 적용할 수 있는 미들웨어로 JWT 로그인을 추가
app.use ((req, res, next) => {
if (!req.cookies.token) {
	next();
    return;  
}

return passport.authenticate('jwt)(req,res,next);
});

JWT 로그아웃

  • 로그아웃은 간단하게 클라이언트 쿠키를 삭제하여 처리 가능
  • token 값을 null로 전달하는 것과 함께 cookie의 만료 시간을 0으로 설정하여 클라이언트가 쿠키를 바로 만료시키도록 전달
res.cookie('token', null, {maxAge:0,});

03. 회원 비밀번호 찾기 구현

회원 비밀번호 찾기 흐름

  1. 임의의 문자열로 비밀번호 초기화
  2. 초기화된 문자열을 메일로 전달 -> 메일 발송 기능 개발 필요
  3. 초기화 후 첫 로그인 시 비밀번호 변경 요청

구현 방법

  1. SMTP 서버 이용 (네이버, 구글 등의 메일 서버)
    -> 직접 구현 필요

  2. 메일 발송 서비스 이용
    -> 메일 발송 API 제공 및 관리용 웹페이지, 사용량에 따라 유료 과금

SMTP

  • SMTP Simple Mail Transfer Protocol
    -> 메일 전송을 위한 표준 규약, SMTP 서버란 표준 규약을 통해 메일을 전송하는 기능을 구현한 서버

Node.js 에서 메일 발송

  • Nodemailer 패키지를 사용하여 SMTP 서버를 통해 메일을 발송할 수 있음
  • SMTP 서버를 직접 만들고 운영하는 것은 비효율적
  • 메일 기능을 제공하는 서비스 제공자들은 SMTP 서버를 사용할 수 있게 제공함(Gmail, Naver)

Nodemailer + Gmail 사용하기

  • Nodemailer에서 Gmai을 사용하기 위해서는 앱 비밀번호 설정 필요
    [구글 계정 설정 -> 보안 -> 앱 비밀번호 추가]
  • 생성된 앱 비밀번호는 다시 확인할 수 없으므로 기록 필수

const nodemailer = require('nodemailer');

const transport = nodemailer
	.createTransport({
	  service: 'Gmail',
      auth:{
      	user: "google account",
        pass: "app password",
      };
});

const message = {
 from: "login account", //발송 메일 주소
 to: "mail address", // 수신 메일 주소
 subject: "title", // 제목
 text: "message" // 내용 
};

transport.sendMail( message, (err, info) => {
	if(err) { // sendMail은 콜백 활용 -> 어싱크나 프로미스로 전환 가능) 
    	console.error('err',err);
        return;
    }
  console.log('ok', info);
})

랜덤 패스워드

function generateRandomPassword(){
	return Math.floor(
    	Math.random() * (10 ** 8)).toString().padStart('0',8);
}
---
router.post('/reset-password',
            asyncHandler(... => {
            const { email } = req.body;
            const randomPassword = generateRandomPassword();
		    await User.findOneAndUpdate({email}, {password : getHash(randomPassword),
                                                 });

			await sendEmail(email, '...', randomPassword);
			res.redirect('/');
                         }));

초기화 후 로그인 시 비밀번호 변경 요청

  • 비밀번호 변경
///비밀번호 변경

const UserSchema = ...
	passwordReset: {
    	type: Boolean,
        default : false,
    }
...
---

  router.post('/reset-password', ...
              await User.findOneAndUpdate({
				...
  			 passwordReset: true,
});
  • 비밀번호 변경 요청
function checkPasswordReset(req, res, next) {
  if(req.user && req.user.passwordReset){
  	res.redirect('/update-password');
    return;
  }
  next();
}
---
router.post('/update-password', ...
            await User.findOneAndUpdate({
			...
  			passwordReset: false,
});

04. OAuth의 이해

OAuth란?

  • Open Authorization : 서비스 제공자가 다른 서비스에게 데이터를 제공하기 위해 서비스 사용자에게 제공하는 사용자 인증 방식의 표준

OAuth 동작 방식

  1. 서비스 제공자에게 인증 요청
  2. 인증 완료 후 사용자 정보를 요청한 서비스로 전달
  3. 인증 정보를 이용해 서비스 제공자의 데이터 사용

OAuth와 로그인

  • OAuth는 사용자의 인증을 제공하는 표준
  • 이를 활용하여 로그인 기능을 간편하게 구성 가능함.
  • 웹 서비스 제공자는 아이디, 비밀번호 로그인을 구현할 필요가 없고, 웹 서비스 사용자는 로그인 시 아이디, 비밀번호를 입력할 필요가 없음.

05. 구글 로그인 구현하기

구글 로그인 구현 순서

  • 구글 클라우드 플랫폼 프로젝트 생성
  • API 및 서비스 -> OAuth 동의화면 설정
  • 사용자 인증정보 -> OAuth 클라이언트 ID 만들기
  • passport-google-oauth20 연동

passport-google-oauth20

  • passport-strategy 인터페이스의 구글 로그인 구현체
  • OAuth 인증을 구현하기 위해서는 인증요청, 데이터 수신 등의 복잡한 작업 필요
  • passport-google-oauth20은 손쉽게 구글 OAuth 2.0을 구현해주는 패키지
const GoogleStrategy = require("passport-google-oauth20").Strategy;

const config = {
  clientID : 'clientID',
  clientSecret: 'clientSecret',
  callbackURL: 'callbackUrl',
}
...

new GoogleStrategy(config, (accessToken, refreshToken, profile, done) => { const {email, name } = profile._json;
                ..                                                         })

passport.use(google);
---
  
router.get('/google', passport.authenticate('google', {
	scope: ['profile', 'email']
}));

06. 추가 Nginx 사용하기

Nginx란?

최근 신규 프로젝트에서 가장 많이 채택되고 있는 웹 서버 소프트웨어
웹 서버 소프트웨어란, HTTP요청을 받아 파일이나 프로그램 실행 결과를 HTTP 응답으로 보내주는 소프트웨어

Nginx를 사용하는 이유

JAVA : Tomcat
PHP : fastcgi등
다른 언어가 HTTP요청을 처리하기 위한 의존성이 있는 것에 반해, Node.js는 기본적으로 HTTP요청을 수신하고 응답하는 기능이 이미 있음.
-> 웹 서버 소프트웨어 없이도 스스로 동작 가능

그러나,

  • HTTPS, 도메인 연결, static file caching 등의 기능을 사용하기 위해 Nginx 같은 웹 서버 소프트웨어는 필수
    -> Node.js 단독으로 production-level 서비스를 구축할 수는 없음.

Nginx + Node.js

  • Nginx의 reverse-proxy 기능을 사용해, Node.js와 Nginx를 연결할 수 있음.
  • reverse-proxy는 HTTP요청을 다른 서버에 전달하는 기능
  • Nginx가 HTTP 요청을 받아서 설정된 내용에 해당하는 요청만 Node.js로 전달.

  • 출처: 엘리스SW엔지니어 트랙
server {
	listen 80;
    server_name www.example.com;
    
 location / {
 	proxy_pass http://localhost: 3000;
    proxy_http_version 1.1;
    }
}
profile
백엔드개발자

0개의 댓글