2026-03-24ยท1๋ถ ์ฝ๊ธฐยท
JWT์ ๊ตฌ์กฐ(Header, Payload, Signature)์ ์ธ์ฆ ํ๋ฆ์ ์ค๋ฌด ๊ด์ ์์ ์ ๋ฆฌํ๊ณ , ์์ ํ๊ฒ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ๊น์ง ํ ๋ฒ์ ์ ๋ฆฌํฉ๋๋ค.
์ด ๊ธ์ด ๋์์ด ๋๋์?
JWT(JSON Web Token)๋ ์๋ฒ์ ํด๋ผ์ด์ธํธ ๊ฐ์ ์ธ์ฆ ์ ๋ณด๋ฅผ ์ ๋ฌํ๊ธฐ ์ํ ํ ํฐ ๊ธฐ๋ฐ ์ธ์ฆ ๋ฐฉ์์ ๋๋ค.
๊ธฐ์กด ์ธ์ ๊ธฐ๋ฐ ์ธ์ฆ๊ณผ ๋น๊ตํ์ ๋ JWT๋ ๋ค์๊ณผ ๊ฐ์ ํน์ง์ ๊ฐ์ง๋๋ค.
ํ์ง๋ง ํธ๋ฆฌํ๋ค๊ณ ํด์ ๋ฌด์กฐ๊ฑด ์์ ํ ๊ฒ์ ์๋๋๋ค. ๊ตฌ์กฐ๋ฅผ ์ ํํ ์ดํดํ๊ณ , ์ ์ฅ ๋ฐฉ์๊ณผ ๊ฒ์ฆ ํ๋ฆ๊น์ง ์ ๋๋ก ์ค๊ณํด์ผ ์ค์ ์ด์์์ ๋ฌธ์ ๋ฅผ ์ค์ผ ์ ์์ต๋๋ค.
JWT๋ ์๋์ ๊ฐ์ 3๊ฐ ํํธ๋ก ๊ตฌ์ฑ๋ฉ๋๋ค.
HEADER.PAYLOAD.SIGNATURE๊ฐ ์์ญ์ . ์ผ๋ก ๊ตฌ๋ถ๋๋ฉฐ, ๊ฐ๊ฐ Base64Url ํ์์ผ๋ก ์ธ์ฝ๋ฉ๋ฉ๋๋ค.
Header์๋ ํ ํฐ์ ํ์ ๊ณผ ์ด๋ค ์๊ณ ๋ฆฌ์ฆ์ผ๋ก ์๋ช ํ๋์ง๊ฐ ๋ค์ด๊ฐ๋๋ค.
{
"alg": "HS256",
"typ": "JWT"
}์ฃผ์ ํ๋:
alg: ์๋ช
์๊ณ ๋ฆฌ์ฆtyp: ํ ํฐ ํ์
Payload์๋ ์ค์ ๋ก ์ ๋ฌํ๊ณ ์ถ์ ๋ฐ์ดํฐ๊ฐ ๋ค์ด๊ฐ๋๋ค.
{
"userId": "zino",
"role": "admin",
"iat": 1710000000,
"exp": 1710003600
}์ฃผ์ ํ๋:
userId: ์ฌ์ฉ์ ์๋ณ์role: ์ฌ์ฉ์ ๊ถํiat: ๋ฐ๊ธ ์๊ฐexp: ๋ง๋ฃ ์๊ฐ์ฃผ์: Payload๋ ์ํธํ๊ฐ ์๋๋ผ ์ธ์ฝ๋ฉ์ ๋๋ค. ๋ฏผ๊ฐํ ๋น๋ฐ๋ฒํธ๋ ๋น๋ฐํค๋ฅผ ๋ฃ์ผ๋ฉด ์ ๋ฉ๋๋ค.
Signature๋ ํ ํฐ ์๋ณ์กฐ๋ฅผ ๋ง๋ ํต์ฌ ๋ถ๋ถ์ ๋๋ค.
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)์๋ฒ๋ ์ด Signature๋ฅผ ๊ฒ์ฆํด์ ํ ํฐ์ด ์ค๊ฐ์ ๋ณ๊ฒฝ๋์ง ์์๋์ง ํ์ธํฉ๋๋ค.
์ผ๋ฐ์ ์ธ ํ๋ฆ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
1. ์ฌ์ฉ์๊ฐ ๋ก๊ทธ์ธ ์์ฒญ
2. ์๋ฒ๊ฐ ์ฌ์ฉ์ ์ธ์ฆ ์ฑ๊ณต
3. ์๋ฒ๊ฐ JWT ๋ฐ๊ธ
4. ํด๋ผ์ด์ธํธ๊ฐ ํ ํฐ ์ ์ฅ
5. ์ดํ ์์ฒญ๋ง๋ค ํ ํฐ ์ ๋ฌ
6. ์๋ฒ๊ฐ ํ ํฐ ๊ฒ์ฆ ํ ์๋ต์๋๋ ์ธ์ฆ ํ๋ฆ ๋ค์ด์ด๊ทธ๋จ์ด๋ ๊ตฌ์กฐ ์ด๋ฏธ์ง๋ฅผ ๋ฃ๊ธฐ ์ข์ ์์น์ ๋๋ค. ๋์ค์ ์ง์ ์ฃผ์๋ง ๋ฐ๊ฟ์ ๋ฃ์ผ๋ฉด ๋ฉ๋๋ค.
์๋๋ jsonwebtoken ํจํค์ง๋ฅผ ์ฌ์ฉํ๋ ๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ์์์
๋๋ค.
import jwt from "jsonwebtoken";
const payload = {
userId: "zino",
role: "admin",
};
const token = jwt.sign(payload, process.env.JWT_SECRET, {
expiresIn: "1h",
issuer: "zino.kr",
});
console.log(token);์ด ์ฝ๋์์ ์ค์ํ ์ ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
JWT_SECRET์ .env.local ๊ฐ์ ํ๊ฒฝ๋ณ์์์ ์ฝ์ด์ผ ํฉ๋๋ค.expiresIn์ ๋ฐ๋์ ์ค์ ํ๋ ๊ฒ์ด ์ข์ต๋๋ค.issuer)๋ audience ๊ฐ์ ์ ๋ณด๋ฅผ ์ถ๊ฐํ๋ฉด ๊ฒ์ฆ์ ๋ ์๊ฒฉํ๊ฒ ํ ์ ์์ต๋๋ค.import jwt from "jsonwebtoken";
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET, {
issuer: "zino.kr",
});
console.log("verified:", decoded);
} catch (error) {
console.error("invalid token", error.message);
}๊ฒ์ฆ ์์๋ ๋จ์ํ ํ ํฐ ๋ฌธ์์ด๋ง ๋ณด๋ ๊ฒ์ด ์๋๋ผ ๋ค์๋ ํจ๊ป ์ ๊ฒํด์ผ ํฉ๋๋ค.
์ค์ ์๋น์ค์์๋ ๋ณดํต ๋ฏธ๋ค์จ์ด ํํ๋ก JWT ๊ฒ์ฆ์ ๋ถ๋ฆฌํฉ๋๋ค.
import jwt from "jsonwebtoken";
export function requireAuth(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith("Bearer ")) {
return res.status(401).json({ ok: false, error: "missing-token" });
}
const token = authHeader.slice(7);
try {
const decoded =
์ด๋ ๊ฒ ํด๋๋ฉด ๋ณดํธ๊ฐ ํ์ํ ๋ผ์ฐํธ์์ ์ฌ์ฌ์ฉํ๊ธฐ ํธํฉ๋๋ค.
JWT๋ฅผ ์ด๋์ ์ ์ฅํ ์ง๋ ๋ณด์์ ํฐ ์ํฅ์ ์ค๋๋ค.
์ฅ์ :
๋จ์ :
์ฅ์ :
๋จ์ :
์ค๋ฌด์์๋ ๋ณดํต HttpOnly + Secure + SameSite ์ฟ ํค ์กฐํฉ์ด ๋ ๊ถ์ฅ๋ฉ๋๋ค.
res.cookie("auth_token", token, {
httpOnly: true,
secure: true,
sameSite: "lax",
path: "/",
maxAge: 1000 * 60 * 60,
});Payload๋ ๋๊ตฌ๋ ๋์ฝ๋ฉํด๋ณผ ์ ์์ต๋๋ค. ๋ฐ๋ผ์ ์๋์ ๊ฐ์ ๊ฐ์ ๋ฃ์ผ๋ฉด ์ ๋ฉ๋๋ค.
ํ ํฐ ํ์ทจ ์ ํผํด ๋ฒ์๊ฐ ์ปค์ง๋๋ค.
์๋ช ๊ฒ์ฆ์ ์๊ฒฉํ๊ฒ ํ์ง ์์ผ๋ฉด ์ฐํ ๊ฐ๋ฅ์ฑ์ด ์๊ธธ ์ ์์ต๋๋ค.
JWT๋ฅผ ์ค๋ฌด์์ ์ธ ๋๋ ๋ณดํต Access Token ํ๋๋ง ์ฐ์ง ์๊ณ , Refresh Token ๊ตฌ์กฐ๋ฅผ ๊ฐ์ด ๋ก๋๋ค.
๋ก๊ทธ์ธ ์ฑ๊ณต
โ Access Token ๋ฐ๊ธ
โ Refresh Token ๋ฐ๊ธ
โ Access Token ๋ง๋ฃ ์ Refresh Token์ผ๋ก ์ฌ๋ฐ๊ธ์ด ๊ตฌ์กฐ๋ฅผ ์ฐ๋ฉด ๋ณด์์ฑ๊ณผ ์ฌ์ฉ์ฑ์ ์ด๋ ์ ๋ ๊ท ํ ์๊ฒ ๋ง์ถ ์ ์์ต๋๋ค.
| ํญ๋ชฉ | JWT | Session |
|---|---|---|
| ์๋ฒ ์ํ ์ ์ฅ | ํ์ ์์ | ํ์ํจ |
| ํ์ฅ์ฑ | ๋์ | ์๋์ ์ผ๋ก ๋ฎ์ |
| ๋ก๊ทธ์์ ์ฒ๋ฆฌ | ์๋์ ์ผ๋ก ์ด๋ ค์ | ์ฌ์ |
| ๊ตฌํ ๋์ด๋ | ์ค๊ฐ | ์ค๊ฐ |
| ํ์ทจ ์ ๋์ | ๋ณ๋ ์ ๋ต ํ์ | ์ธ์ ๋ง๋ฃ/์ญ์ ๊ฐ๋ฅ |
JWT๊ฐ ๋ฌด์กฐ๊ฑด ๋ ์ข์ ๊ฒ์ ์๋๋๋ค. ์๋น์ค ๊ตฌ์กฐ์ ์๊ตฌ์ฌํญ์ ๋ฐ๋ผ ์ ํํด์ผ ํฉ๋๋ค.
JWT์ ๋ํ์ ์ธ ๋จ์ ์ค ํ๋๋ ํ ๋ฒ ๋ฐ๊ธํ ํ ํฐ์ ์๋ฒ๊ฐ ์ฆ์ ํ๊ธฐํ๊ธฐ ์ด๋ ต๋ค๋ ์ ์ ๋๋ค.
๋ํ์ ์ธ ๋์ ๋ฐฉ์:
์๋ฅผ ๋ค์ด DB์ ํ ํฐ ๋ฒ์ ์ ๋๊ณ ์ฌ์ฉ์๊ฐ ๋น๋ฐ๋ฒํธ๋ฅผ ๋ฐ๊พธ๋ฉด ๋ฒ์ ์ ์ฌ๋ ค ๊ธฐ์กด ํ ํฐ์ ๋ฌดํจ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.
const payload = {
userId: user.id,
tokenVersion: user.tokenVersion,
};๊ฒ์ฆ ์ DB์ ํ์ฌ tokenVersion๊ณผ ๋น๊ตํ๋ฉด ๋ฉ๋๋ค.
JWT๋ฅผ ์ด์์ ๋ฃ๊ธฐ ์ ์ ์๋ ์ ๋๋ ๊ผญ ํ์ธํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
JWT๋ ํ๋ ์น ์๋น์ค์์ ๋งค์ฐ ๋๋ฆฌ ์ฌ์ฉ๋์ง๋ง, ๊ตฌ์กฐ๋ฅผ ๋ชจ๋ฅด๊ณ ๋ณต๋ถ๋ง ํ๋ฉด ์คํ๋ ค ๋ณด์ ๋ฌธ์ ๊ฐ ์๊ธธ ์ ์์ต๋๋ค.
ํต์ฌ๋ง ๋ค์ ์ ๋ฆฌํ๋ฉด:
JWT๋ ํธ๋ฆฌํ ๋๊ตฌ์ด์ง๋ง, ์ด๋ป๊ฒ ์ ์ฅํ๊ณ ์ด๋ป๊ฒ ๊ฒ์ฆํ๋๋๊ฐ ์ง์ง ํต์ฌ์ ๋๋ค.
Inline code: jwt.sign(payload)
Link: JWT ๊ณต์ ์ฌ์ดํธ
Bold text / Italic text / Strikethrough
export const token = jwt.sign(payload, secret);const veryLongAuthorizationHeaderExample = "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.long-long-long-long-long-token-value-for-overflow-test";โ ๏ธ Warning This is a multiline blockquote used for testing rendering.
| Feature | Description | Notes |
|---|---|---|
| JWT | Token | Basic |
| OAuth | Framework | Complex |
| Long text example | This is a very long text to test wrapping behavior in table cells | Wrap test |

JWT Raw HTML Link
JWT๋ ์ํ ์๋ ์ธ์ฆ์ ๊ฐ๋ ฅํ์ง๋ง, ๋ง๋ฃ์๊ฐยท์๋ช ๊ฒ์ฆยท์ ์ฅ๋ฐฉ์๊น์ง ํจ๊ป ์ค๊ณํด์ผ ์์ ํ๊ฒ ์ฌ์ฉํ ์ ์์ต๋๋ค.
Comments
๋๊ธ
๋๊ธ์ ๋จ๊ธฐ๋ ค๋ฉด ๋ก๊ทธ์ธ์ด ํ์ํด์. (๋ค์ด๋ฒ ยท ๊ตฌ๊ธ ๊ณ์ )
๋๊ธ ๋ถ๋ฌ์ค๋ ์คโฆ