티스토리 뷰
JWT
▶ JWT 란?
JSON Web Token
: 인증을 위한 정보를 특별한 저장소를 이용하지 않고, 전자 서명을 이용하여 확인하는 방법
▶ JWT의 구성
- header : 토큰의 타입(jwt), 데이터 서명 방식
- payload : 전달되는 데이터
- signature : header 와 payload의 전자서명
JWT 는 Web Token
→ 데이터를 웹에서 사용하기 위한 스펙이므로 웹에서 문제없이 사용할 수 있는 문자열로만 구성된 base64 인코딩을 사용
참고) JWT 홈페이지
▶ 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기 강의를 들으며 정리한 내용입니다.
'개발공부 > 🟩 Node.js' 카테고리의 다른 글
[NodeJS] Express와 MongoDB로 웹서비스 만들기 - 3 (2) (0) | 2022.08.05 |
---|---|
[NodeJS] Express와 MongoDB로 웹서비스 만들기 - 2 (2) (0) | 2022.08.05 |
[NodeJS] Express와 MongoDB로 웹서비스 만들기 - 2 (1) (0) | 2022.08.04 |
[NodeJS] Express와 MongoDB로 웹서비스 만들기 - 1 (2) (0) | 2022.08.02 |
[NodeJS] Express와 MongoDB로 웹서비스 만들기 - 1 (1) (0) | 2022.08.02 |
프론트엔드 개발자 삐롱히의 개발 & 공부 기록 블로그