JWT(JSON Web Token)
์ธํฐ๋ท ํ์ค ์ธ์ฆ ๋ฐฉ์์ผ๋ก, JSON ๊ฐ์ฒด์ ์ธ์ฆ ์ ๋ณด๋ฅผ ๋ด๊ณ ํ ํฐ์ผ๋ก ์ํธํํ ๊ฒ
* ํ ํฐ(Token) : ์ถ์ ์ฆ ์ญํ ์ ํ๋ ๋๊ตฌ๋ก, ํด๋ผ์ด์ธํธ๊ฐ ์์งํ๊ณ ์์
๊ตฌ์กฐ
๊ฒ์ฆ ๋ฐฉ์
- Header์ Payload๋ฅผ ๊ฐ๊ฐ base64๋ก ์ธ์ฝ๋ฉ
- ์ธ์ฝ๋ฉํ Header์ Payload๋ฅผ ํฉ์ณ์, Secret(์๋ฒ๋ง ์๊ณ ์๋ ๋น๋ฐ ํค)์ผ๋ก ์๋ช (Signature ์์ฑ)
- ์๋ฒ๋ ์ ๋ฌ๋ฐ์ JWT์ Signature๊ฐ Secret์ผ๋ก ๋ค์ ๊ณ์ฐํ ๊ฒ๊ณผ ์ผ์นํ๋์ง ํ์ธ
→ ์ผ์นํ๋ฉด ์์กฐ๋์ง ์์ ์ ํจํ ํ ํฐ์ผ๋ก ํ๋จ
* ์ด ๋ base64 ์ธ์ฝ๋ฉ ๋ฐฉ์์ ์ผ๋ง๋ ์ง ๋์ฝ๋ฉ์ด ๊ฐ๋ฅํ๊ธฐ ๋๋ฌธ์, payload์ ๋ฏผ๊ฐํ ์ ๋ณด๋ฅผ ๋ฃ์ง ์๋๋ก ํด์ผํ๋ค.
๊ณต์ ํํ์ด์ง์์ ํ ํฐ์ ์ ํจ์ฑ์ ํ์ธํด๋ณผ ์ ์๋ค. base64๋ก ์ธ์ฝ๋ฉํ๋ ์ฌ์ดํธ๋ ์ฌ๊ธฐ
ํ ํฐ์ ์ข ๋ฅ
- ์ก์ธ์ค ํ ํฐ : ๊ถํ ์ธ์ฆ์ฉ
- ๋ฆฌํ๋ ์ ํ ํฐ : ์ก์ธ์ค ํ ํฐ ์ฌ๋ฐ๊ธ์ฉ
ํ ํฐ ์ธ์ฆ์ ํ๋ฆ
1) ์ ์์ ์ผ๋ก ์ธ์ฆ์ด ์๋ฃ๋์ ๋
2) ํ ํฐ ์ธ์ฆ์ ์ด์์ด ์๊ฒผ์ ๋
์ค์ต์ ์ฟ ํค ๊ธฐ๋ฐ ์ธ์ฆ๊ณผ ํ ํฐ ๊ธฐ๋ฐ ์ธ์ฆ ๋ฐฉ์ ๋ ๊ฐ์ง๊ฐ ์๋ค.
๋๋ค ์ด์ ์ธ์ ์ค์ต์์ ํ๋ ๋ก๊ทธ์ธ ๊ธฐ๋ฅ์ ์ธ์ ๋ถ๋ถ์ ํ ํฐ์ผ๋ก ์ฌ๊ตฌํํ๋๋ก ํ๋ค.
์ค์ต - ์ฟ ํค ๊ธฐ๋ฐ ์ธ์ฆ ๋ฐฉ์
์๋ฒ๊ฐ ์ธ์ฆ ํ ํด๋ผ์ด์ธํธ์ ์ฟ ํค๋ก ํ ํฐ์ ์ฃผ๋ ๋ฐฉ์์ด๋ค.
- ํด๋ผ์ด์ธํธ๋ ์ด ์ฟ ํค๋ฅผ ๋ธ๋ผ์ฐ์ ๊ฐ ์๋์ผ๋ก ์ ์ฅํจ.
- ์ดํ ์์ฒญ๋ง๋ค ์๋์ผ๋ก Cookie ํค๋์ ์ฟ ํค๋ฅผ ํฌํจํจ.
- ์ฌ์ฉ์๋ ์ ๊ฒฝ ์ ์จ๋ ๋จ (๋ธ๋ผ์ฐ์ ๊ฐ ์์์ ์ฟ ํค ์ ์ก).
server.js
const express = require('express');
const cors = require('cors');
const cookieParser = require('cookie-parser');
const jwt = require('jsonwebtoken');
const users = [
{
user_id: 'test',
user_password: '1234',
user_name: 'ํ
์คํธ์ ์ ',
user_info: '์ ๋ ํ
์คํธ์ ์ ์
๋๋ค...',
},
];
const app = express();
app.use(
cors({
origin: ['http://127.0.0.1:5500', 'http://localhost:5500'],
methods: ['POST', 'GET', 'DELETE', 'OPTIONS'],
credentials: true, // ์ฟ ํค ์ ์ฅ์ ์ํด ํ์
}),
);
app.use(cookieParser());
app.use(express.json());
// app.use(
// session({
// // ์ํธํ๋ฅผ ์ํด ์ค์ ํ๋ ๋น๋ฐ ์ฝ๋
// secret: 'session secret',
// resave: false, // request๊ฐ ๋ ๋๋ง๋ค ์ธ์
๋ฐ์ดํฐ๋ฅผ ํญ์ ๋ค์ ์ ์ฅํ ์ง ์ฌ๋ถ (๋ณ๊ฒฝ์ด ์์ด๋)
// // ์ด๊ธฐํ๋์ง ์์ ์ธ์
์ ์ ์ฅํ ์ง ์ฌ๋ถ (์ธ์
์ ์๋ฌด ๋ด์ฉ์ด ์์ด๋ ์ ์ฅํ ์ง)
// saveUninitialized: false,
// name: 'session_id',
// }),
// );
const secretKey = 'ozcodingschool';
session๋ง๊ณ jwt์ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ session ๋ถ๋ถ์ ์ญ์ ํ๊ณ jwt ๋ชจ๋์ require๋ก ๋ถ๋ฌ์๋ค.
๊ทธ ์ธ express()๋ก ์๋ฒ ๊ฐ์ฒด ์์ฑ์ด๋ cors ์ค์ ๋ถ๋ถ, ์ฟ ํค๋ json ๋ถ๋ถ์ ๊ทธ๋๋ก ์ฌ์ฉํ๋ฉฐ session ๋ถ๋ถ์ ์ฌ์ฉํ์ง ์๋๋ค.
๊ทธ๋ฆฌ๊ณ ํ ํฐ์ ์ฌ์ฉํ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ secretKey ๊ฐ์ ์์ฑํ๋ค.
app.post()
// ๋ผ์ฐํ
app.post('/', (req, res) => {
const { userId, userPassword } = req.body;
const userInfo = users.find(
(el) => el.user_id === userId && el.user_password === userPassword,
);
if (!userInfo) {
res.status(401).send('๋ก๊ทธ์ธ ์คํจ');
} else {
// sign() : ํ ํฐ ์์ฑ ํจ์
// expiresIn : ํ ํฐ์ ์ ํจ๊ธฐ๊ฐ
// ์ง๊ธ์ 10๋ถ์ผ๋ก ์ค์
const accessToken = jwt.sign({ userId: userInfo.user_id }, secretKey, {
expiresIn: 1000 * 60 * 10,
});
console.log(accessToken);
// ์ธ์
๋ถ๋ถ ์ฌ์ฉX
// req.session.userId = userInfo.user_id;
// ํ ํฐ ๊ฐ์ ์ฟ ํค๋ก ์ ์ฅ
res.cookie('accessToken', accessToken);
res.send('ํ ํฐ ์์ฑ ์๋ฃ');
}
});
์ผ๋จ accessToken๊ฐ ์ด๋ป๊ฒ ๊ตฌ์ฑ๋๋์ง ํ์ธํด๋ณด๋ฉด
์ด๋ ๊ฒ ๋์จ๋ค... ์ผ๋จ . ์ ๊ธฐ์ค์ผ๋ก ๋๋๋ฉด
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJ1c2VySWQiOiJ0ZXN0IiwiaWF0IjoxNzQ3MzI1MDM1LCJleHAiOjE3NDc5MjUwMzV9.
U-SYp3267fZKH23SL5r_Gw298PthzjdMHuOe6uY0I3g
์ค๊ฐ๋ถ๋ถ์ธ eyJ1c ~~ ๋ถ๋ถ์ด payload ๋ถ๋ถ์ด ๋๋ค. ์ด๊ฑธ ์์ ์ธ๊ธํ base64 ์ธ์ฝ๋ฉ ์ฌ์ดํธ์์ ํ์ธํด๋ณด๋ฉด
userId ๊ฐ์ ์ ํ์ธํ ์ ์๋ค!
์ด ๋ iat๋ ํ ํฐ์ด ์ธ์ ์์ฑ๋๋์ง, exp๋ ์ธ์ ๋ง๊ธฐ๋๋์ง์ ๊ฐ์ด๋ผ์
exp - lat๋ฅผ ๊ณ์ฐํด๋ณด๋ฉด 600000(๋ฐ๋ฆฌ์ด ๋จ์๋ผ 10๋ถ์ ์๋ฏธ)์ด ๋์จ๋ค
์๋ฌดํผ ์ฟ ํค๋ ์ ์์ฑ๋๊ณ ์๋ ๊ฒ์ ํ์ธํ ์ ์๋ค!
app.get()
app.get('/', (req, res) => {
// ๊ตฌ์กฐ๋ถํดํ ๋น ๋ฌธ๋ฒ ์ด์ฉ
const { accessToken } = req.cookies;
// verify() : ํ ํฐ์ ๊ฒ์ฆํ ๋ ์ฌ์ฉ
// payload ๋ถ๋ถ์ base64ํ์์ ๋์ฝ๋ฉํ ๊ฒ๊ณผ ๋์ผํ ๊ฐ์ ๋ฐํํ๋ค
const payload = jwt.verify(accessToken, secretKey);
const userInfo = users.find((el) => el.user_id === payload.userId);
return res.json(userInfo);
});
app.delete()
app.delete('/', (req, res) => {
// ์ฟ ํค์ ํ ํฐ ์ ๋ณด๊ฐ ๋ค์ด์์ผ๋ฏ๋ก ์ฟ ํค๋ง ์ญ์
res.clearCookie('accessToken');
res.send('ํ ํฐ ์ญ์ ์๋ฃ');
});
์๋ฌดํผ ์ฟ ํค ๊ธฐ๋ฐ ์ธ์ฆ ๋ฐฉ์์ server.js ์ฝ๋๋ ์๋์ ๊ฐ๋ค
const express = require('express');
const cors = require('cors');
const cookieParser = require('cookie-parser');
const jwt = require('jsonwebtoken');
const users = [
{
user_id: 'test',
user_password: '1234',
user_name: 'ํ
์คํธ์ ์ ',
user_info: '์ ๋ ํ
์คํธ์ ์ ์
๋๋ค...',
},
];
const app = express();
app.use(
cors({
origin: ['http://127.0.0.1:5500', 'http://localhost:5500'],
methods: ['POST', 'GET', 'DELETE', 'OPTIONS'],
credentials: true, // ์ฟ ํค ์ ์ฅ์ ์ํด ํ์
}),
);
app.use(cookieParser());
app.use(express.json());
const secretKey = 'ozcodingschool';
// ๋ผ์ฐํ
app.post('/', (req, res) => {
const { userId, userPassword } = req.body;
// ๋ก๊ทธ์ธ ์ฑ๊ณต ์ฌ๋ถ๋ฅผ userInfo์ ๋ด์
const userInfo = users.find(
(el) => el.user_id === userId && el.user_password === userPassword,
);
if (!userInfo) {
res.status(401).send('๋ก๊ทธ์ธ ์คํจ');
} else {
// expiresIn : ํ ํฐ์ ์ ํจ๊ธฐ๊ฐ
// ์ง๊ธ์ 10๋ถ์ผ๋ก ์ค์
const accessToken = jwt.sign({ userId: userInfo.user_id }, secretKey, {
expiresIn: 1000 * 60 * 10,
});
console.log(accessToken);
res.cookie('accessToken', accessToken);
res.send('ํ ํฐ ์์ฑ ์๋ฃ');
}
});
app.get('/', (req, res) => {
const { accessToken } = req.cookies;
// verify() : ํ ํฐ์ ๊ฒ์ฆํ ๋ ์ฌ์ฉ
// payload ๋ถ๋ถ์ base64ํ์์ ๋์ฝ๋ฉํ ๊ฒ๊ณผ ๋์ผํ ๊ฐ์ ๋ฐํํ๋ค
const payload = jwt.verify(accessToken, secretKey);
const userInfo = users.find((el) => el.user_id === payload.userId);
return res.json(userInfo);
});
app.delete('/', (req, res) => {
// ์ฟ ํค์ ํ ํฐ ์ ๋ณด๊ฐ ๋ค์ด์์ผ๋ฏ๋ก ์ฟ ํค๋ง ์ญ์
res.clearCookie('accessToken');
res.send('ํ ํฐ ์ญ์ ์๋ฃ');
});
app.listen(3000, () => console.log('์๋ฒ ์คํ'));
์ค์ต - Bearer Token ๋ฐฉ์
์ฟ ํค์ ์ ์ฅํ์ง ์๊ณ , ์๋ฐ์คํฌ๋ฆฝํธ ๋ณ์(localStorage, sessionStorage, ๋๋ React state ๋ฑ)์ ์ ์ฅํด ์ง์ Authorization ํค๋์ ๋ถ์ด๋ ๋ฐฉ์
- ๋ก๊ทธ์ธ ์ ์๋ฒ์์ **ํ ํฐ(JWT ๋ฑ)**์ ์๋ต์ผ๋ก ๋ณด๋.
- ํด๋ผ์ด์ธํธ๊ฐ ์ด ํ ํฐ์ js ๋ณ์์ ์ ์ฅ.
- ์ดํ API ์์ฒญ ์ ์ง์ ํค๋์ ํฌํจ์ํด.
app.post()
server.js
// ๋ผ์ฐํ
app.post('/', (req, res) => {
const { userId, userPassword } = req.body;
// ๋ก๊ทธ์ธ ์ฑ๊ณต ์ฌ๋ถ๋ฅผ userInfo์ ๋ด์
const userInfo = users.find(
(el) => el.user_id === userId && el.user_password === userPassword,
);
if (!userInfo) {
res.status(401).send('๋ก๊ทธ์ธ ์คํจ');
} else {
const accessToken = jwt.sign({ userId: userInfo.user_id }, secretKey, {
expiresIn: 1000 * 60 * 10,
});
// ์๋ต์ผ๋ก ๋ฐ๋ก ํ ํฐ์ ์ ์ก
res.send(accessToken);
}
});
์ด๋ฒ์ ์ฟ ํค๋ฅผ ์ฌ์ฉํ์ง ์๊ณ ๋ฐ๋ก ์๋ต์ผ๋ก ํ ํฐ์ ์ ์กํ๋ค.
login.js
// ํด๋ผ์ด์ธํธ์์ ์ฟ ํค ๋์ ๊ด๋ฆฌํด์ค ๋ณ์
let accessToken = '';
function login() {
const userId = idInput.value;
const userPassword = passwordInput.value;
return axios
.post('http://localhost:3000', { userId, userPassword })
.then((res) => (accessToken = res.data));
}
๋ฐ๋ก ํ ํฐ์ ์ ์กํ์ผ๋ฏ๋ก ์๋ต์ ๋ฐ์์จ ๋ค ์ฝ์์ฐฝ์ ์ถ๋ ฅํ๋ค.
์๋ต์ด ์ ๋์ฐฉํ ๊ฒ๊น์ง ํ์ธํ์ผ๋ ์ด์ ์ฟ ํค ๋์ ๋ณ์์ ์ ์ฅํด ๋ถ๋ฌ์ค๋๋ก ํด์ผ๋๋ค.
app.get()
login.js
function getUserInfo() {
return axios.get('http://localhost:3000', {
// ์ด๊ฒ ์ ํด์ง ์์์ด๋ฏ๋ก ์ด๋ ๊ฒ ๋ณด๋ด์ค์ผํจ!!
headers: { Authorization: `Bearer ${accessToken}` },
});
}
server.js
app.get('/', (req, res) => {
const accessToken = req.headers.authorization.split(' ')[1];
const payload = jwt.verify(accessToken, secretKey);
const userInfo = users.find((el) => el.user_id === payload.userId);
return res.json(userInfo);
});
์ง๊ธ ์ฝ๋์์๋ ๊ตณ์ด Bearer๋ฅผ ์จ์ ๊ตณ์ด ๊ตณ์ด ๊ตณ์ด... split(' ')[1]ํด์ ๊ฐ์ ธ์ค๊ณ ์๋๋ฐ ๋จ์ ๊ธฐ์ ์ ์ผ๋ก๋ ํ์ฌ ์ฝ๋์์๋ ์์จ๋ ๋๊ธดํ์ง๋ง ํ์ค์ ๋ง์ถ๊ธฐ ์ํด ์ด๋ค๊ณ ํ๋ค.
* Authorization ํค๋๋ HTTP ํ์ค์ ๋ฐ๋ผ ์ฌ๋ฌ ๋ฐฉ์์ ์ธ์ฆ ์ ๋ณด๋ฅผ ๋ณด๋ผ ์ ์๊ฒ ๋์ด ์๋๋ฐ ๊ทธ ์ค ํ๋๊ฐ Bearer ํ ํฐ ๋ฐฉ์์ด๋ผ Bearer๋ฅผ ๋จผ์ ์ฐ๊ณ ๊ทธ ๋ค์ ํ ํฐ์ ์ฐ๋ ์์ผ๋ก ์ฌ์ฉํ๊ณ ์๋ค.
app.delete()
์ฌ์ค ์ง๊ธ์ ๊ทธ๋ฅ ๋ณ์์ ๋ด์๋๊ณ ์๊ธฐ ๋๋ฌธ์ delete() ๋ผ์ฐํ ์์ฒด๊ฐ ํ์๊ฐ ์๋ค.
๊ทธ๋์ ํด๋ผ์ด์ธํธ(login.js) ์์ ๊ทธ๋ฅ accessToken ๊ฐ์ ๋น์๋๋ฉด ํด๊ฒฐ๋๋ค.
login.js
function logout() {
accessToken = '';
}
logoutBtn.onclick = () => {
logout();
renderLoginForm();
};
์ฟ ํค vs ์ธ์ vs ํ ํฐ
'Node.js' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
Node.js / ์ธ์ (Session) (0) | 2025.05.14 |
---|---|
Node.js / ์ฟ ํค(Cookie) (0) | 2025.05.14 |
Node.js / ๋คํธ์ํฌ ๊ธฐ์ด (0) | 2025.05.12 |