ํฐ์คํ ๋ฆฌ ๋ทฐ
2025.02.21 (๊ธ) Works

20์ผ ๊ตฌํ ์ผ์ ์ค์ ๋๋ฌด์ง ์ดํด๊ฐ ์ ๊ฐ ๋๊ฒผ๋ JWT ์ธ์ฆ API ์ฐ๊ฒฐ.
JWT ์ธ์ฆ ์๋ฒ์ ํต์ ํ๋ ์ฝ๋๋ฅผ ์์ฑํด์ผ ํ๋ค.
JWT
๋ฆฌ์กํธ ์ฌํ ๊ฐ์ ์ค์ API ์ฐ๊ฒฐ์ ๊ดํ ๋ด์ฉ์ ์๊ฒ ๋์๋ค.
JWT์ ๋ํด ์ดํดํ๋ ค๋ฉด 'ํ ํฐ'์ ๋จผ์ ์์์ผ ํ๋ค.
ํ ํฐ์ด๋?
- ํด๋ผ์ด์ธํธ์์ ๋ณด๊ดํ๋ ์ํธํ ๋๋ ์ธ์ฝ๋ฉ๋ ์ธ์ฆ ์ ๋ณด
- ์๋ฒ์ ์ํ๋ฅผ ์ ์งํ์ง ์๊ณ ๋ ํด๋ผ์ด์ธํธ์ ์ธ์ฆ ์ํ๋ฅผ ํ์ธ ๊ฐ๋ฅ
- ์ธ์ ์ฒ๋ผ ์๋ฒ์์ ์ฌ์ฉ์์ ์ธ์ฆ ์ ๋ณด๋ฅผ ๋ณด๊ดํ ํ์๊ฐ ์๊ธฐ ๋๋ฌธ์ ์๋ฒ ๋ถ๋ด์ ์ค์ฌ์ฃผ๋ ์ธ์ฆ ์๋จ
- ์น์์ ์ธ์ฆ ์๋จ์ผ๋ก ์ฌ์ฉ๋๋ ํ ํฐ์ ์ฃผ๋ก JWT (Json Web Token) ์ ์ด์ฉ
์ฝ๊ฒ ์ค๋ช ํ์๋ฉด ์ฐ์ง๋ฐฉ์์ ๋ฐ๋ ํค๋ผ๊ณ ์๊ฐํ๋ฉด ๋๋ค.

์ด์ฉ์๋ ํค๋ฅผ ๊ฐ์ง๊ณ ๊ณ๋๋ ์ฌ๋จน๊ณ , ์ํ๋ ์ฌ๋จน๊ณ ํ ์ ์๋ ๊ฒ์ด๋ค.
ํ ๋ง๋๋ก ํ ํฐ์ด ์์ด์ผ ํ์ด์ง์์ ๋ค์ํ ๊ธฐ๋ฅ์ ์ด์ฉํ ์ ์๋ ๊ฒ์ด๋ค.
JWT๋?
- JWT๋ ํ ํฐ ๊ธฐ๋ฐ ์ธ์ฆ ๋ฐฉ์์์ ์ฌ์ฉ๋๋ ํน๋ณํ ํ ํฐ
- ๋ก๊ทธ์ธํ ์ฌ์ฉ์๊ฐ ์ดํ์ ์๋ฒ์ ์์ฒญ์ ๋ณด๋ผ ๋ ์ฌ์ฉ
- ์ธ ๊ฐ์ง ์ฃผ์ ๋ถ๋ถ์ผ๋ก ๋๋์ด์ ธ ์์(header, payload, signature)
JWT์ ์ธ ๊ฐ์ง ๋ถ๋ถ

- ํค๋(Header): ์ด๋ค ์ข ๋ฅ์ ํ ํฐ์ธ์ง์ ์ด๋ค ์๊ณ ๋ฆฌ์ฆ์ผ๋ก ์๋ช ๋์๋์ง์ ๋ํ ์ ๋ณด๊ฐ ๋ค์ด์๋ค.
- ๋ณธ๋ฌธ(Payload): ์ค์ ๋ก ์ค์ํ ๋ฐ์ดํฐ๊ฐ ๋ค์ด์๋ ๋ถ๋ถ์ ๋๋ค. ์๋ฅผ ๋ค์ด, ์ฌ์ฉ์ ID, ํ ํฐ์ ๋ง๋ฃ ์๊ฐ ๋ฑ์ด ์ฌ๊ธฐ์ ํฌํจ๋๋ค.
- ์๋ช (Signature): ์ด ๋ถ๋ถ์ ํ ํฐ์ด ์์กฐ๋์ง ์์๋์ง ํ์ธํ๋ ์ญํ ์ ํฉ๋๋ค. ์๋ฒ๋ง์ด ์ ์ ์๋ ๋น๋ฐ ํค๋ก ์๋ช ๋์ด ์๋ค. ์ด ์๋ช ๋๋ฌธ์ ํ ํฐ์ ๋ฌด๊ฒฐ์ฑ์ด ๋ณด์ฅ๋๋ค.
๋ด๊ฐ ์ดํดํ ๋ฐ๋ก๋
JWT ๋ผ๋ ํ ํฐ์ ๊ฐ์ง๊ณ ์ฌ์ฉ์๊ฐ ํ๋ฉด์์ ๋์ํ ๊ฒ๋ค์ ๋ด์ฉ(๋ก๊ทธ์ธ, ํ์๊ฐ์ , ํ์ ์ ๋ณด, ํ ์คํธ ๊ฒฐ๊ณผ) ๋ฑ์ ์๋ฒ๋ก ๊ณต์ ํ์ฌ ๋ฐฑ์๋๋ก ๋ณด๋ด๋ ์ฒ๋ฆฌ ๊ณผ์ ์ธ ๋ฏ ํ๋ค.
API ์ฐ๊ฒฐ
์ ๋ณด๋ฅผ ํ๋ก ํธ์์ ๋ฐฑ์ผ๋ก ๋๊ธฐ๊ธฐ ์ํด์๋ ์ฝ์์ด ํ์ํ๋ค.
๋ฐฑ์๋ ์ธก์์ ์๋ฒ URL์ ํตํด ์์ฒญํ๊ณ ๋ฐ๋ ๋ฐฉ๋ฒ์ ์ ๋ฆฌํ API ๋ฌธ์๋ฅผ ๋ฐํ์ผ๋ก ํ๋ก ํธ ์ธก์์ ๋ก์ง์ ์์ฑํ๋ฉด ๋๋ค.
์๋๋ API ๋ฌธ์์ ์์์ด๋ค.
์๋ฒ API_URL
https://www. (์ดํ ์๋ต)
1. ํ์๊ฐ์
- ์์ด๋, ๋น๋ฐ๋ฒํธ, ๋๋ค์์ผ๋ก DB์ ๋ณธ์ธ์ ํ์์ ๋ณด๋ฅผ ์ ์ฅ
- Request
- Method → POST
- URL PATH → /register
body
JSON
{
"id": "์ ์ ์์ด๋",
"password": "์ ์ ๋น๋ฐ๋ฒํธ",
"nickname": "์ ์ ๋๋ค์"
}
- Response
{
"message": "ํ์๊ฐ์
์๋ฃ",
"success": true
}
2. ๋ก๊ทธ์ธ
- ์์ด๋์ ๋น๋ฐ๋ฒํธ๊ฐ DB์ ์๋ ํ์์ ๋ณด์ ์ผ์นํ๋ฉด
accessToken, userId, avatar, nickname ์ด 4๊ฐ์ง ์ ์ ์ ๋ณด๋ฅผ ์๋ตํด ์ค - Request
- Method → POST
- URL PATH → /login
body
JSON
{
"id":"์ ์ ์์ด๋",
"password": "์ ์ ๋น๋ฐ๋ฒํธ"
}
Query string โฌ๏ธ (์ ํ)
- accessToken ์ ํจ์๊ฐ ์กฐ์ ์ ์ํ query string
- query string ์์ด path๋ก๋ง ์์ฒญ ์ ๊ธฐ๋ณธ 1์๊ฐ
- query string (expiresIn) ์ผ๋ก ์๊ฐ ๊ธฐ์ ์ ํด๋น ์๊ฐ๋๋ก ํ ํฐ ์ ํจ์๊ฐ ์กฐ์ ๊ฐ๋ฅ
- expiresIn : ์๊ฐ ๋จ์๋ฅผ ๋ถ์ธ ๋ฌธ์์ด ex) 10s 10m 10h
- TIP) ํ ํฐ๋ง๋ฃ ์ ๋ก๊ทธ์์ ์ฒ๋ฆฌ๋๋ ๋ก์ง์ ํ ์คํธํ ๋ ์ฌ์ฉ
/login?expiresIn=10m
// ์ ํจ์๊ฐ์ 10๋ถ์ธ accessToken ์์ฒญ
- Response
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImFiY2FiYyIsImlhdCI6MTcwMDgxNDQyMCwiZXhwIjoxNzAwODE4MDIwfQ.8hWOHHEzDPzumnqCU7jyoi3zFhr-HNZvC7_pzBfOeuU",
"userId": "์ ์ ์์ด๋",
"success": true,
"avatar": "ํ๋กํ ์ด๋ฏธ์ง",
"nickname": "์ ์ ๋๋ค์"
}
3. ํ์์ ๋ณด ํ์ธ
- accessToken์ด ์ ํจํ ๊ฒฝ์ฐ, ๋น๋ฐ๋ฒํธ๋ฅผ ์ ์ธํ ๋ณธ์ธ์ ํ์์ ๋ณด๋ฅผ ์๋ตํด ์ค
// authorization ์์ฑ ์ ์
const response = await axios.get(`${BASE_URL}/user`, {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
});
- Request
- Method → GET
- URL PATH → /user
Header
- Response
{
"id": "์ฌ์ฉ์ ์์ด๋",
"nickname": "์ฌ์ฉ์ ๋๋ค์",
"avatar": null,
"success": true
}
4. ํ๋กํ ๋ณ๊ฒฝ
- accessToken์ด ์ ํจํ ๊ฒฝ์ฐ, ํ๋กํ ์ด๋ฏธ์ง ๋๋ ๋๋ค์์ FormData์ ํตํด ์์ฒญํ๋ฉด
๋ณ๊ฒฝ ์๋ฃ๋ ์ด๋ฏธ์ง URL๊ณผ ๋๋ค์์ ์๋ตํด ์ค
// ์ด๋ฏธ์งํ์ผ์ FormData์ ๋ด๋ ๋ฐฉ๋ฒ
const formData = new FormData();
// avatar์ nickname ์ค ํ๋ ๋๋ ๋ชจ๋ ๋ณ๊ฒฝ ๊ฐ๋ฅ
formData.append("avatar", imgFile);
formData.append("nickname", nickname);
// ์์ฒญ ์ Content-Type์ ์ ์
const response = await axios.patch(`${BASE_URL}/profile`, formData, {
headers: {
"Content-Type": "multipart/form-data",
Authorization: `Bearer ${accessToken}`,
},
});
- Request
- Method → PATCH
- URL PATH → /profile
Header
{
"Authorization": "Bearer AccessToken"
}
Body
FORM
{
"avatar": [์ด๋ฏธ์งํ์ผ],
"nickname": "๋ณ๊ฒฝํ ๋๋ค์"
}
- Response
{
"avatar": "๋ณ๊ฒฝ๋ ์ด๋ฏธ์ง URL",
"nickname": "๋ณ๊ฒฝ๋ ๋๋ค์",
"message": "ํ๋กํ์ด ์
๋ฐ์ดํธ๋์์ต๋๋ค.",
"success": true
}


Thuder Client์ Postman (HTTP ์์ฒญ์ ๋ณด๋ด๊ณ ์๋ต์ ๋ฐ๋๋ฐ ํ์ํ ๋ชจ๋ ๊ธฐ๋ฅ์ ๊ฐ์ง๊ณ ์๋ ํด)
์ ํตํด API ํ ์คํธ๋ฅผ ํ ์ ์์๋ค.
๊ธฐ๋ณธ ์ฌ์ฉ ๋ฐฉ๋ฒ
1. New Request ํด๋ฆญ

2. ์ํ๋ HTTP Method ์ ํ ๋ฐ URL ์ ๋ ฅ
2-1. Payload๊ฐ ํ์ํ ๊ฒฝ์ฐ Body์ ์ถ๊ฐ

2-2. [Send]๋ฅผ ๋๋ฌ ์๋ต ํ์ธ

๐ ์์ ๋ด์ฉ์ ๋ฐํ์ผ๋ก ์ด๋ฒ ๊ณผ์ ์์ ๊ตฌํํ ๋ก์ง์ ๋ค์๊ณผ ๊ฐ๋ค.
// auth.js
import axios from "axios";
const authApi = axios.create({
baseURL: "https://www. //์ดํ์๋ต
});
// ํ์๊ฐ์
export const register = async (userData) => {
const response = await authApi.post("/register", userData);
return response.data;
};
// ๋ก๊ทธ์ธ
export const login = async (userData) => {
const response = await authApi.post("/login", userData);
return response.data;
};
// ํ์ ์ ๋ณด ํ์ธ
export const getUserProfile = async (token) => {
const response = await authApi.get("/user", {
headers: { Authorization: `Bearer ${token}` },
});
return response.data;
};
// ํ๋กํ ๋ณ๊ฒฝ
export const updateProfile = async (token, formData) => {
const response = await authApi.patch("/profile", formData, {
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "multipart/form-data",
},
});
return response.data;
};
export default authApi;
// AuthForm.jsx
// ๋ก๊ทธ์ธ ํจ์
const handleLogin = async (e) => {
e.preventDefault();
try {
const data = await login({ ๐๐ปlogin ์ฌ์ฉ
id: userInput.id,
password: userInput.password,
});
if (data.success) {
sessionStorage.setItem("accessToken", data?.accessToken);
setUserInfo({
id: data.userId, // ๋ฐฑ์๋์์๋ ์ฌ์ฉ์์ ์์ด๋๋ฅผ 'userId'๋ผ๋ ์ด๋ฆ์ผ๋ก ๊ด๋ฆฌํจ
nickname: data.nickname,
});
setIsAuthenticated(true);
const formData = new FormData();
formData.append("nickname", data.nickname);
formData.append("userId", data.userId);
await updateProfile(data.accessToken, formData);
navigation("/");
} else {
alert("๋ก๊ทธ์ธ์ ์คํจํ์ต๋๋ค!");
}
} catch (error) {
console.error("Login error", error);
alert(error.response.data.message);
}
};
// ํ์๊ฐ์
ํจ์
const handleSignup = async (e) => {
e.preventDefault();
console.log("ํ์๊ฐ์
์์ฒญ:", userInput);
try {
const data = await register({ ๐๐ปregister ์ฌ์ฉ
id: userInput.id,
password: userInput.password,
nickname: userInput.nickname,
});
if (data.success) {
alert("ํ์๊ฐ์
์ฑ๊ณต!");
navigation("/login");
} else {
alert("ํ์๊ฐ์
์ ์คํจํ์ต๋๋ค!");
}
} catch (error) {
console.error("Signup error", error);
alert(error.response.data.message);
}
userInput("");
};
// Profile.jsx
// DB: userprofile ๊ฐ์ ธ์ค๊ธฐ
useEffect(() => {
if (!isAuthenticated) {
alert("๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค.");
navigate("/login");
} else {
const fetchUserInfo = async () => {
try {
const data = await getUserProfile(token); ๐๐ป getUserProfile ์ฌ์ฉ
setUserInfo({
id: data.id,
nickname: data.nickname,
});
} catch (error) {
console.log("Faild to fetch user info", error);
}
};
fetchUserInfo();
}
}, []);
// Profile.jsx
// ๋๋ค์ ๋ณ๊ฒฝ
const handleNickNameChange = async (e) => {
e.preventDefault();
try {
const formData = new FormData();
formData.append("nickname", newNickName);
const data = await updateProfile(token, formData); ๐๐ป updateProfile ์ฌ์ฉ
if (data.success) {
setUserInfo((prev) => ({
...prev,
nickname: data.nickname,
}));
alert("๋๋ค์์ด ๋ณ๊ฒฝ๋์์ต๋๋ค.");
setUserInfo({ ...userInfo, nickname: newNickName });
setNewNickName("");
} else {
alert("๋๋ค์ ๋ณ๊ฒฝ์ ์คํจํ์ต๋๋ค.");
}
} catch (error) {
console.error("Failed to update nickname", error);
alert(error.response.data.message);
}
};'Project' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| [MBTI-Test PJ_Final] MBTI Test (0) | 2025.02.25 |
|---|---|
| [MBTI-Test PJ_Day 3] Visibility, isOwner (0) | 2025.02.25 |
| [MBTI-Test PJ_Day 1] Tailwind ๊ณตํต ์ปดํฌ๋ํธ ์คํ์ผ๋ง (1) | 2025.02.21 |
| [NewsFeed-Final] FootPrint๐ฃ (1) | 2025.02.18 |
| [NewsFeed-Day 1] 10์กฐ์ ์ฒซ ๋ฐ์๊ตญ..๐ฃ (0) | 2025.02.12 |