ํ‹ฐ์Šคํ† ๋ฆฌ ๋ทฐ

2025.02.21 (๊ธˆ) Works

 

20์ผ ๊ตฌํ˜„ ์ผ์ • ์ค‘์— ๋„๋ฌด์ง€ ์ดํ•ด๊ฐ€ ์•ˆ ๊ฐ€ ๋„˜๊ฒผ๋˜ JWT ์ธ์ฆ API ์—ฐ๊ฒฐ.

JWT ์ธ์ฆ ์„œ๋ฒ„์™€ ํ†ต์‹ ํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ–ˆ๋‹ค.

 

 

JWT

 

๋ฆฌ์•กํŠธ ์‹ฌํ™” ๊ฐ•์˜ ์ค‘์— API ์—ฐ๊ฒฐ์— ๊ด€ํ•œ ๋‚ด์šฉ์„ ์•Œ๊ฒŒ ๋˜์—ˆ๋‹ค.

JWT์— ๋Œ€ํ•ด ์ดํ•ดํ•˜๋ ค๋ฉด 'ํ† ํฐ'์„ ๋จผ์ € ์•Œ์•„์•ผ ํ•œ๋‹ค.

 

ํ† ํฐ์ด๋ž€?

 

  • ํด๋ผ์ด์–ธํŠธ์—์„œ ๋ณด๊ด€ํ•˜๋Š” ์•”ํ˜ธํ™” ๋˜๋Š” ์ธ์ฝ”๋”ฉ๋œ ์ธ์ฆ ์ •๋ณด
  • ์„œ๋ฒ„์˜ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•˜์ง€ ์•Š๊ณ ๋„ ํด๋ผ์ด์–ธํŠธ์˜ ์ธ์ฆ ์ƒํƒœ๋ฅผ ํ™•์ธ ๊ฐ€๋Šฅ
  • ์„ธ์…˜์ฒ˜๋Ÿผ ์„œ๋ฒ„์—์„œ ์‚ฌ์šฉ์ž์˜ ์ธ์ฆ ์ •๋ณด๋ฅผ ๋ณด๊ด€ํ•  ํ•„์š”๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์— ์„œ๋ฒ„ ๋ถ€๋‹ด์„ ์ค„์—ฌ์ฃผ๋Š” ์ธ์ฆ ์ˆ˜๋‹จ
  • ์›น์—์„œ ์ธ์ฆ ์ˆ˜๋‹จ์œผ๋กœ ์‚ฌ์šฉ๋˜๋Š” ํ† ํฐ์€ ์ฃผ๋กœ JWT (Json Web Token) ์„ ์ด์šฉ

์‰ฝ๊ฒŒ ์„ค๋ช…ํ•˜์ž๋ฉด ์ฐœ์งˆ๋ฐฉ์—์„œ ๋ฐ›๋Š” ํ‚ค๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋œ๋‹ค.

 

์ด์šฉ์ž๋Š” ํ‚ค๋ฅผ ๊ฐ€์ง€๊ณ  ๊ณ„๋ž€๋„ ์‚ฌ๋จน๊ณ , ์‹ํ˜œ๋„ ์‚ฌ๋จน๊ณ  ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด๋‹ค.

ํ•œ ๋งˆ๋””๋กœ ํ† ํฐ์ด ์žˆ์–ด์•ผ ํŽ˜์ด์ง€์—์„œ ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ์„ ์ด์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด๋‹ค.

 

 

JWT๋ž€?

 

  • JWT๋Š” ํ† ํฐ ๊ธฐ๋ฐ˜ ์ธ์ฆ ๋ฐฉ์‹์—์„œ ์‚ฌ์šฉ๋˜๋Š” ํŠน๋ณ„ํ•œ ํ† ํฐ
  • ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž๊ฐ€ ์ดํ›„์— ์„œ๋ฒ„์— ์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ ์‚ฌ์šฉ
  • ์„ธ ๊ฐ€์ง€ ์ฃผ์š” ๋ถ€๋ถ„์œผ๋กœ ๋‚˜๋‰˜์–ด์ ธ ์žˆ์Œ(header, payload, signature)
๋”๋ณด๊ธฐ
๋”๋ณด๊ธฐ

JWT์˜ ์„ธ ๊ฐ€์ง€ ๋ถ€๋ถ„

 

  1. ํ—ค๋”(Header): ์–ด๋–ค ์ข…๋ฅ˜์˜ ํ† ํฐ์ธ์ง€์™€ ์–ด๋–ค ์•Œ๊ณ ๋ฆฌ์ฆ˜์œผ๋กœ ์„œ๋ช…๋˜์—ˆ๋Š”์ง€์— ๋Œ€ํ•œ ์ •๋ณด๊ฐ€ ๋“ค์–ด์žˆ๋‹ค.
  2. ๋ณธ๋ฌธ(Payload): ์‹ค์ œ๋กœ ์ค‘์š”ํ•œ ๋ฐ์ดํ„ฐ๊ฐ€ ๋“ค์–ด์žˆ๋Š” ๋ถ€๋ถ„์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์‚ฌ์šฉ์ž ID, ํ† ํฐ์˜ ๋งŒ๋ฃŒ ์‹œ๊ฐ„ ๋“ฑ์ด ์—ฌ๊ธฐ์— ํฌํ•จ๋œ๋‹ค.
  3. ์„œ๋ช…(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);
    }
  };
๊ณต์ง€์‚ฌํ•ญ
์ตœ๊ทผ์— ์˜ฌ๋ผ์˜จ ๊ธ€
์ตœ๊ทผ์— ๋‹ฌ๋ฆฐ ๋Œ“๊ธ€
Total
Today
Yesterday
๋งํฌ
TAG
more
ยซ   2026/03   ยป
์ผ ์›” ํ™” ์ˆ˜ ๋ชฉ ๊ธˆ ํ† 
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
๊ธ€ ๋ณด๊ด€ํ•จ