티스토리 뷰

개발공부/🟩 Node.js

[NodeJS] Express와 MongoDB로 웹서비스 만들기 - 3 (1)

2022. 8. 5. 20:13

 

 

Node.js

Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine.

nodejs.org

 

 

 

 JWT 

 

▶ JWT 란?

 

JSON Web Token

:  인증을 위한 정보를 특별한 저장소를 이용하지 않고, 전자 서명을 이용하여 확인하는 방법

 

 

 

 

▶ JWT의 구성

 

-  header  :  토큰의 타입(jwt), 데이터 서명 방식

-  payload  :  전달되는 데이터

-  signature  :  header 와 payload의 전자서명

 

 

JWT 는 Web Token 

→  데이터를 웹에서 사용하기 위한 스펙이므로 웹에서 문제없이 사용할 수 있는 문자열로만 구성된 base64 인코딩을 사용

 

이미지 캡쳐 - https://jwt.io/

 

참고)  JWT 홈페이지

 

JWT.IO

JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.

jwt.io

 

 

 

▶ JWT와 보안

 

JWT의 payload는 단순히 정보를 base64 encode한 String이기 때문에 decode를 하면 정보가 노출됨 

→   민감한 정보는 제외하고 토큰 생성 필수

 

서버는 JWT를 생성할 때 비공개키를 이용하여 서명을 함

→   payload를 조작할 경우 서명이 일치하지 않기 때문에 인증 실패

 

 

 

 

▶ JWT의 작동방식

 

사용자 로그인  →   서버는 로그인된 유저 정보를 JWT로 생성하여 클라이언트에 전달

→   클라이언트는 전달받은 JWT를 이용하여 인증이 필요한 요청에 사용

 

 

 

 

▶ JWT의 사용 이유

 

이전 수업까지는 로그인할 때 session을 이용했었는데

session은 기본적으로 웹 브라우저의 통신 스펙

 →   모바일 앱 등 웹 브라우저가 아닌 어플리케이션의 경우 이를 활용하기 부적합

 

JWT를 사용하면 어느 클라이언트에서나 동일한 방식의 사용자 인증을 구현 가능

 

 

 

 

▶  JWT + Cookie 사용하기

 

-  Cookie 란 ?

:  웹 서비스에서 사용하는 정보를 클라이언트에 저장하고

HTTP 요청 시 이를 함께 전송하여 클라이언트 정보를 서버에 전달하는 기술

 

 

 

Cookie  vs.  Session

 

Cookie  -  클라이언트 정보를 클라이언트(브라우저)에 저장하고 사용

Session  -  클라이언트 정보를 서버 측 저장소에 저장하고 사용

 

 

1)  Session을 사용한 유저 로그인의 경우

:  Cookie에 Session ID 저장  →  서버로 Session ID 전달  →  Session Store에서 전달받은 Session ID로 유저정보 검색 후 사용

 

 

 

2)  JWT를 사용한 유저 로그인의 경우

:  Cookie에 JWT 저장  →  JWT로 요청  →  서버에서 JWT 서명 확인 후 유저 정보 사용

 

 

≫  JWT를 사용하면 Session을 사용했을 때보다 데이터베이스 접근이 줄어서 효율적인 인증 구현 가능

 

 

 

 

 

 

 

 JWT + Cookie 를 사용하여 로그인 구현하기 

 

 

▶  JWT 로그인 구현하기

 

1)  기존에 세션으로 구현했던 로그인을 비활성화

 

-  로그인 로직에서 JWT 생성 후 쿠키로 전달

-  passport-jwt 패키지로 JWT 로그인 미들웨어 작성 및 사용

// app.use(session(...));
// app.use(passport.session());

 

 

 

2)  로그인 로직에서 JWT 생성 후 쿠키로 전달

 

-  res.cookie 함수 사용하여 token을 클라이언트에 쿠키로 전달

setUserToken = (res, user) => {
    const token = jwt.sign(user, secret);
    res.cookie('token', token);
}

---
router.post('/', passport.authenticate('local'), (req, res, next) => {
    setUserToken(res, req.user);
    res.redirect('/');
});

 

 

 

3)  passport-jwt 패키지로 JWT 로그인 미들웨어 작성 및 사용

 

- passport-jwt 패키지를 이용해 요청된 JWT 토큰의 서명을 확인하고 인증하는 기능 구현

const JwtStrategy = require('passport-jwt').Strategy;
    const cookieExtractor = (req) => {
		const { token } = req.cookies;
		return token;
	};
    
	const opts = {
		secretOrKey: secret,
		jwtFromRequest: cookieExtractor,
	}
    
module.exports = new JwtStrategy(opts, (user, done) => {
	done(null, user);
});

---
passport.use(jwt);

 

-  JWT 토큰은 기본적으로 모든 요청에 포함

-  요청에 토큰이 있는 경우 로그인된 상태로 처리하기 위해 모든 요청에 공통적으로 적용할 수 있는 미들웨어로 JWT 로그인을 추가

app.use((req, res, next) => {
    if (!req.cookies.token) {
        next();
        return;
    }
    
	return passport.authenticate('jwt')(req, res, next);
});

 

 

 

4)  로그아웃

 

-  로그아웃은 간단하게 클라이언트 쿠키를 삭제하여 처리 가능

-  token값을 null로 전달하는 것과 함께 cookie의 만료 시간을 0으로 설정하여 클라이언트가 쿠키를 바로 만료시키도록 전달

res.cookie('token', null, {
	maxAge: 0,
});

 

 

 

 

 

 

 회원 비밀번호 찾기 구현하기 

 

▶ 비밀번호 찾기 흐름

 

1)  임의의 문자열로 비밀번호 초기화

2)  초기화된 문자열을 메일로 전달  →  메일 발송기능 개발 필요

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

 

 

 

▶ 메일 발송기능 구현 방법

 

방법 1)  SMTP 서버 이용

-  네이버 구글 등의 메일서버를 이용하여 무료로 발송 가능

-  메일 발송 및 관리 기능 직접 개발 필요

 

방법 2)  메일 발송 서비스 이용 (Mailgun, Sendgrid, ... )

-  메일 발송 api 제공 및 관리용 웹페이지 제공

-  사용량에 따라 유료 과금

 

 

 

* SMTP 란?

 

Simple Mail Transfer Protocol

:  메일 전송을 위한 표준 규약

 

→  SMTP 서버란 표준 규약을 통해 메일을 전송하는 기능을 구현한 서버

 

-  SMTP 서버를 직접 만들고 운영하는 것은 비효율적

-  메일 기능을 제공하는 Gmail, Naver 등의 서비스 제공자들은 SMTP 서버를 사용할 수 있게 제공함

-  Nodemailer 패키지를 사용하여 SMTP 서버를 통해 메일을 발송 가능

 

 

 

 

▶ Nodemailer + Gmail 사용하기

 

-  Nodemailer에서 Gmail을 사용하기 위해서는 앱 비밀번호 설정 필요!

-  구글 계정설정  →  보안  →  앱 비밀번호 추가

-  생성된 앱 비밀번호는 다시 확인할 수 없으므로 기록 필수

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) {
		console.error('err', err);
		return;
	}
	console.log('ok', info);
});

 

 

 

 

▶ 비밀번호 찾기 기능 구현하기

 

 

1)  임의의 문자열로 비밀번호 초기화 / 초기화 후 메일 발송

 

-  generateRandomPassword()는 임의의 문자열을 만들어주는 함수

-  사용자의 email을 받아서 해당 사용자의 비밀번호를 임의로 생성된 문자열로 초기화

-  초기화한 비밀번호를 메일로 발송

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('/');
}));

 

 

 

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

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,
});

 

 

 

 


 이 글은 엘리스의 AI트랙 5기 강의를 들으며 정리한 내용입니다.

반응형
프로필사진
개발자 삐롱히

프론트엔드 개발자 삐롱히의 개발 & 공부 기록 블로그