๐ŸŽฏ JWT ๊ธฐ๋ฐ˜์œผ๋กœ ์ธ์ฆ, ์ธ๊ฐ€ํ•˜๋Š” ๊ฐœ๋…์„ ์ •๋ฆฌํ•˜๊ณ , ์ฝ”๋“œ์— ๋ฐ˜์˜ํ•ฉ๋‹ˆ๋‹ค.


๐Ÿ“™ Today I Learned

๐Ÿ”‘ ์ธ์ฆ vs ์ธ๊ฐ€

์ธ์ฆ (Authentication)

  • ์‚ฌ์šฉ์ž๊ฐ€ ์‚ฌ์ดํŠธ์— ๊ฐ€์ž…๋œ ์‚ฌ์šฉ์ž์ž„์„ ์ฆ๋ช…ํ•˜๋Š” ๊ณผ์ •์ž…๋‹ˆ๋‹ค.

    • ์˜ˆ : ๋กœ๊ทธ์ธ

์ธ๊ฐ€ (Authorization)

  • ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž๊ฐ€ ํŠน์ • ํŽ˜์ด์ง€๋‚˜ ๊ธฐ๋Šฅ์— ์ ‘๊ทผํ•  ๊ถŒํ•œ์ด ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๊ณผ์ •์ž…๋‹ˆ๋‹ค.

    • ์˜ˆ: ๊ด€๋ฆฌ์ž ํŽ˜์ด์ง€์™€ ์ผ๋ฐ˜ ์‚ฌ์šฉ์ž ํŽ˜์ด์ง€ ๊ตฌ๋ถ„



๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์„œ๋ฒ„์—์„œ ๋ฐ›์€ ์ž‘์€ ๋ฐ์ดํ„ฐ ์กฐ๊ฐ(key-value)์„ ์ €์žฅํ•ด๋‘๊ณ , ์ดํ›„ ๊ฐ™์€ ์„œ๋ฒ„์— ์š”์ฒญํ•  ๋•Œ ์ž๋™์œผ๋กœ ๋‹ค์‹œ ๋ณด๋‚ด์ค๋‹ˆ๋‹ค.

๋™์ž‘ ๋ฐฉ์‹

  1. ๋กœ๊ทธ์ธ โ†’ ์„œ๋ฒ„๊ฐ€ ์ฟ ํ‚ค ์ƒ์„ฑ (์˜ˆ: userId=123)

  2. ์„œ๋ฒ„๊ฐ€ ์ฟ ํ‚ค๋ฅผ ๋ธŒ๋ผ์šฐ์ €์— ์ „์†ก (Set-Cookie)

  3. ๋ธŒ๋ผ์šฐ์ €๋Š” ์ฟ ํ‚ค๋ฅผ ์ €์žฅํ•˜๊ณ  ์ดํ›„ ์š”์ฒญ๋งˆ๋‹ค ์ฟ ํ‚ค ์ž๋™ ์ „์†ก

  4. ์„œ๋ฒ„๋Š” ์ฟ ํ‚ค ๊ฐ’์„ ์ฝ์–ด์„œ ์œ ์ € ์‹๋ณ„ or ์„ค์ •๊ฐ’ ํ™•์ธ

ํŠน์ง•

  • ๋ธŒ๋ผ์šฐ์ €์— ์ €์žฅ๋จ (Client-side)

  • ๊ธฐ๋ณธ์ ์œผ๋กœ ์š”์ฒญ๋งˆ๋‹ค ์ž๋™ ์ „์†ก๋จ โ†’ ๋ณด์•ˆ ์ฃผ์˜ ํ•„์š”

  • ์šฉ๋Ÿ‰ ์ œํ•œ ์žˆ์Œ (๋„๋ฉ”์ธ๋‹น 4KB๊นŒ์ง€)


์›น ํŽ˜์ด์ง€์˜ F12๋ฅผ ๋ˆŒ๋Ÿฌ ๊ฐœ๋ฐœ์ž ๋„๊ตฌ ์ฐฝ > Application > Cookies ์—์„œ ์ฟ ํ‚ค๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


Expires ํƒญ์—์„œ Session์ด๋ผ๊ณ  ์ ํ˜€์žˆ๋Š” ์ฟ ํ‚ค๋Š” ์„ธ์…˜ ์ฟ ํ‚ค๋ฅผ ๋œปํ•ฉ๋‹ˆ๋‹ค.




๐Ÿ” ์„ธ์…˜ (Session)

์ฟ ํ‚ค์˜ ๋ณด์•ˆ ๋ฌธ์ œ๋ฅผ ๋ณด์™„ํ•˜๊ธฐ ์œ„ํ•ด ๋“ฑ์žฅํ•œ ๋ฐฉ์‹์œผ๋กœ ์„œ๋ฒ„๊ฐ€ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์„œ๋ฒ„ ๋ฉ”๋ชจ๋ฆฌ/DB์— ์ €์žฅํ•˜๊ณ , ๋ธŒ๋ผ์šฐ์ €์—๋Š” ๋‹จ์ˆœํžˆ ์„ธ์…˜ID(Session ID)๋งŒ ์ฟ ํ‚ค๋กœ ๋ฐœ๊ธ‰ํ•˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.

๋™์ž‘ ๋ฐฉ์‹

  1. ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธํ•˜๋ฉด ์„œ๋ฒ„๊ฐ€ ํŠน์ • ์ •๋ณด๋ฅผ ์ €์žฅํ•˜๋Š” ์„ธ์…˜์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

  2. ์„ธ์…˜ ID๋ฅผ ์ฟ ํ‚ค๋กœ ์‚ฌ์šฉ์ž์—๊ฒŒ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.

  3. ์ดํ›„ ์‚ฌ์šฉ์ž๋Š” ์š”์ฒญ ์‹œ ์„ธ์…˜ ID๋ฅผ ํฌํ•จํ•˜์—ฌ ์„œ๋ฒ„์™€ ํ†ต์‹ ํ•ฉ๋‹ˆ๋‹ค.

ํŠน์ง•

  • ๋น„๊ต์  ๋ณด์•ˆ์ด ๋›ฐ์–ด๋‚ฉ๋‹ˆ๋‹ค. (์ฟ ํ‚ค๋ณด๋‹ค ์•ˆ์ „)

  • ์„œ๋ฒ„์—์„œ ์œ ์ € ์ƒํƒœ๋ฅผ ๊ธฐ์–ต(Stateful) โ†’ ์„œ๋ฒ„์— ์ €์žฅ์†Œ ํ•„์š”




๐Ÿš€ JWT (JSON Web Token)

JWT๋Š” JSON ํ˜•์‹์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ์ „์†กํ•˜๋Š” ํ† ํฐ์œผ๋กœ, ์ธ์ฆ๊ณผ ์ธ๊ฐ€์— ์ž์ฃผ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

๋™์ž‘ ๋ฐฉ์‹

  1. ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์„œ๋ฒ„์— ๋กœ๊ทธ์ธ ์š”์ฒญ์„ ๋ณด๋ƒ…๋‹ˆ๋‹ค.

  2. ์„œ๋ฒ„๊ฐ€ ์‚ฌ์šฉ์ž์˜ ๋กœ๊ทธ์ธ ์ •๋ณด๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

  3. ์„œ๋ฒ„๊ฐ€ ๋กœ๊ทธ์ธ ์ •๋ณด๊ฐ€ ์œ ํšจํ•˜๋ฉด JWT๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ํด๋ผ์ด์–ธํŠธ์— ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.

  4. ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋ฐ›์€ JWT๋ฅผ ์ €์žฅํ•œ ์ดํ›„ ์„œ๋ฒ„์— ์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ JWT๋ฅผ ํฌํ•จํ•˜์—ฌ ์ „์†กํ•ฉ๋‹ˆ๋‹ค.

  5. ์„œ๋ฒ„๋Š” ๋ฐ›์€ JWT์˜ ์œ ํšจ์„ฑ์„ ๊ฒ€์ฆํ•œ ํ›„ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๊ณ  ์‘๋‹ตํ•ฉ๋‹ˆ๋‹ค.


 [๐Ÿ–ฅ๏ธํด๋ผ์ด์–ธํŠธ]                       [๐Ÿข์„œ๋ฒ„]
   |                                 |
1๏ธโƒฃ ๋กœ๊ทธ์ธ ์š”์ฒญ -------------------> 2๏ธโƒฃ ๋กœ๊ทธ์ธ ์ •๋ณด ํ™•์ธ
   |                                 |
   | <---------------------------- 3๏ธโƒฃ JWT ๋ฐœ๊ธ‰ ๋ฐ ์ „๋‹ฌ
   |                                 |
4๏ธโƒฃ ์š”์ฒญ ์‹œ JWT ํฌํ•จ ---------------> 5๏ธโƒฃ ์„œ๋ฒ„์—์„œ JWT ๊ฒ€์ฆ ๋ฐ ์‘๋‹ต
   |
(๐Ÿ” ์ธ์ฆ๋œ ์ƒํƒœ ์œ ์ง€)

ํŠน์ง•

  • ๋ณ€์กฐ๋ฅผ ๊ฐ์ง€ํ•  ์ˆ˜ ์žˆ์–ด ๋ณด์•ˆ์„ฑ์ด ๋›ฐ์–ด๋‚ฉ๋‹ˆ๋‹ค.

  • ์„œ๋ฒ„๊ฐ€ ๋ณ„๋„๋กœ ์ •๋ณด๋ฅผ ์ €์žฅํ•˜์ง€ ์•Š์•„๋„ ๋˜๋ฏ€๋กœ Statelessํ•œ ๊ตฌ์กฐ์ž…๋‹ˆ๋‹ค.

  • ๋ณ„๋„์˜ ํ† ํฐ ๋ฐœ๊ธ‰ ์„œ๋ฒ„๋ฅผ ์šด์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.




๐Ÿ— JWT ๊ตฌ์กฐ

JWT๋Š” ํ—ค๋”(Header), ํŽ˜์ด๋กœ๋“œ(Payload), ์„œ๋ช…(Signature)์œผ๋กœ ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค.

๊ฐ ๋ถ€๋ถ„์€ .์œผ๋กœ ๊ตฌ๋ถ„๋˜๋ฉฐ, ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ˜•์‹์ž…๋‹ˆ๋‹ค.

์ธ์ฝ”๋”ฉ ๊ฐ’

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.KMUFsIDTnFmyG3nMiGM6H9FNFUROf3wh7SmqJp-QV30

๐Ÿ‘‰ jwt.io ์—์„œ JWT ๊ตฌ์กฐ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


ํ—ค๋”(Header)

JWT์˜ ํƒ€์ž…๊ณผ ์•”ํ˜ธํ™” ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์ •๋ณด๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.

{   
  "alg": "HS256",   
  "typ": "JWT" 
}
  • alg : ์‚ฌ์šฉ๋œ ํ•ด์‹ฑ ์•Œ๊ณ ๋ฆฌ์ฆ˜

  • typ : ํ† ํฐ ํƒ€์ž…


ํŽ˜์ด๋กœ๋“œ(Payload)

์‚ฌ์šฉ์ž ์ •๋ณด ๋ฐ ํด๋ ˆ์ž„(๊ถŒํ•œ ๋“ฑ)์ด ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.

{   
  "sub": "1234567890",   
  "name": "John Doe",   
  "admin": true,   
  "iat": 1516239022 
}
  • sub : ์‚ฌ์šฉ์ž ID

  • name : ์‚ฌ์šฉ์ž ์ด๋ฆ„

  • admin : ๊ด€๋ฆฌ์ž ์—ฌ๋ถ€

  • iat(Issued At) : ํ† ํฐ ๋ฐœํ–‰ ์‹œ๊ฐ„์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.


์„œ๋ช…(Signature)

JWT ๋ณ€์กฐ ๋ฐฉ์ง€๋ฅผ ์œ„ํ•œ ์„œ๋ช…(Signature) ์ •๋ณด์ž…๋‹ˆ๋‹ค.

a-string-secret-at-least-256-bits-long



์‹ค์Šต: JWT ๋กœ๊ทธ์ธ ๊ตฌํ˜„

๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ค์น˜

$ npm install jsonwebtoken dotenv cookie-parser

๋กœ๊ทธ์ธ API ์ฝ”๋“œ

const express = require('express');
const router = express.Router();
const conn = require('../mariadb');
const { body, validationResult } = require('express-validator');
const jwt = require('jsonwebtoken');
const dotenv = require('dotenv');
dotenv.config();

router.use(express.json());

const validate = (req, res, next) => {
  const err = validationResult(req);

  if (err.isEmpty()) {
    return next();
  } else {
    return res.status(400).json(err.array());
  }
};

router.post(
  '/login',
  [
    body('email').notEmpty().isEmail().withMessage('์ด๋ฉ”์ผ ์ž…๋ ฅ ํ•„์š”'),
    body('password').notEmpty().isString().withMessage('๋น„๋ฐ€๋ฒˆํ˜ธ ์ž…๋ ฅ ํ•„์š”'),
    validate,
  ],
  (req, res) => {
    const { email, password } = req.body;

    const sql = 'SELECT * FROM users WHERE email =?';
    conn.query(sql, email, (err, results) => {
      if (err) {
        return res.status(400).end();
      }

      let loginUser = results[0];

      if (loginUser && loginUser.password === password) {
        const token = jwt.sign(
          {
            email: loginUser.email,
            name: loginUser.name,
          },
          process.env.PRIVATE_KEY,
          {
            expiresIn: '30m',
            issuer: 'chickenmandu',
          }
        );

        res.cookie('token', token, { httpOnly: true });

        res.status(200).json({ message: `${loginUser.name}๋‹˜ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค.` });
      } else {
        res.status(403).json({
          message: '์ด๋ฉ”์ผ ๋˜๋Š” ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ํ‹€๋ ธ์Šต๋‹ˆ๋‹ค.',
        });
      }
    });
  }
);

  • loginUser๊ฐ€ ์กด์žฌํ•˜๊ณ  ์ž…๋ ฅํ•œ ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ DB์˜ ๋น„๋ฐ€๋ฒˆํ˜ธ์™€ ์ผ์น˜ํ•˜๋ฉด ๋กœ๊ทธ์ธ ์„ฑ๊ณตํ•˜๊ฒŒ ๋˜์–ด jwt.sign()์„ ์‚ฌ์šฉํ•ด JWT ๋ฐœ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค.

    • email, name : ํ† ํฐ์— ํฌํ•จํ•  ์‚ฌ์šฉ์ž ์ •๋ณด์ž…๋‹ˆ๋‹ค.

    • process.env.PRIVATE_KEY : .env ํŒŒ์ผ์— ์ €์žฅ๋œ ๋น„๋ฐ€ํ‚ค๋กœ ์„œ๋ช…ํ•ฉ๋‹ˆ๋‹ค.

    • expiresIn: '30m' : ํ† ํฐ ๋งŒ๋ฃŒ ์‹œ๊ฐ„์„ 30๋ถ„์œผ๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.

    • issuer: 'chickenmandu' : ํ† ํฐ ๋ฐœ๊ธ‰์ž๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.


  • res.cookie('token', token, { httpOnly: true }) : HTTP ์ „์šฉ ์ฟ ํ‚ค๋กœ JWT ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.



  • Name : ์„œ๋ฒ„์—์„œ token์ด๋ผ๋Š” ์ด๋ฆ„์˜ ์ฟ ํ‚ค๋ฅผ ์„ค์ •

  • Value : JWT(ํ† ํฐ) ๊ฐ’์ด ์„ค์ •๋˜์–ด ์žˆ์Œ

  • Domain : ๋กœ์ปฌ ํ™˜๊ฒฝ์—์„œ ์ฟ ํ‚ค๊ฐ€ ์„ค์ •๋จ

  • Path: ๋ชจ๋“  ๊ฒฝ๋กœ์—์„œ ์ด ์ฟ ํ‚ค๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Œ

  • Expires : ๋งŒ๋ฃŒ๊ฐ€ ์„ธ์…˜ ์ฟ ํ‚ค๋กœ ์„ค์ •๋จ (๋ธŒ๋ผ์šฐ์ €๋ฅผ ๋‹ซ์œผ๋ฉด ์‚ญ์ œ๋จ)

  • HTTPOnly : ๋ธŒ๋ผ์šฐ์ €์˜ JavaScript๋กœ ์ ‘๊ทผ ๋ถˆ๊ฐ€๋Šฅ โ†’ XSS ๊ณต๊ฒฉ ๋ฐฉ์ง€

  • Secure : HTTPS ํ™˜๊ฒฝ์—์„œ๋งŒ ์ฟ ํ‚ค ์ „์†ก ๊ฐ€๋Šฅ โ†’ ๋„คํŠธ์›Œํฌ ์Šค๋‹ˆํ•‘ ๋ฐฉ์ง€

๐Ÿค”์™œ JWT๋ฅผ ์ฟ ํ‚ค๐Ÿช์— ๋‹ด๋Š” ๊ฑธ๊นŒ?

JWT๋ฅผ ์ €์žฅํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•(localStorage, sessionStorage, Cookie)์€ ์—ฌ๋Ÿฌ๊ฐ€์ง€์ง€๋งŒ, ์ฟ ํ‚ค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ด์œ ๋Š” ๋ณด์•ˆ๊ณผ ํŽธ์˜์„ฑ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

  • XSS ๊ณต๊ฒฉ ๋ฐฉ์–ด (HttpOnly ์†์„ฑ)
  • ์ž๋™ ์ธ์ฆ ๊ด€๋ฆฌ (์š”์ฒญ ์‹œ ์ž๋™์œผ๋กœ ํฌํ•จ๋จ)
  • ๋ณด์•ˆ ๊ฐ•ํ™” ๊ฐ€๋Šฅ
  • ๋ธŒ๋ผ์šฐ์ € ๊ธฐ๋ณธ ๊ธฐ๋Šฅ ํ™œ์šฉ ๊ฐ€๋Šฅ



์ฟ ํ‚ค vs ์„ธ์…˜ vs JWT ๋น„๊ต


๊ตฌ๋ถ„์ฟ ํ‚ค (Cookie)์„ธ์…˜ (Session)JWT (Json Web Token)
์ €์žฅ ์œ„์น˜๋ธŒ๋ผ์šฐ์ € (Client)์„œ๋ฒ„(์„ธ์…˜ ์ €์žฅ์†Œ) + ๋ธŒ๋ผ์šฐ์ €(์„ธ์…˜ID)๋ธŒ๋ผ์šฐ์ €(LocalStorage, SessionStorage, Cookie ๋“ฑ)
์„œ๋ฒ„ ์ƒํƒœ ์œ ์ง€ ์—ฌ๋ถ€โŒ Statelessโœ… StatefulโŒ Stateless
์ธ์ฆ ๋ฐฉ์‹์ฟ ํ‚ค ์ž์ฒด์— ๋ฐ์ดํ„ฐ ์ €์žฅ์„ธ์…˜ID๋งŒ ์ฟ ํ‚ค์— ์ €์žฅ โ†’ ์„œ๋ฒ„์—์„œ ์œ ์ € ์ƒํƒœ ์กฐํšŒํ† ํฐ ์ž์ฒด์— ์œ ์ € ์ •๋ณด ํฌํ•จ โ†’ ์„œ๋ฒ„๋Š” ๊ฒ€์ฆ๋งŒ
ํ™•์žฅ์„ฑ(Scale)๋†’์Œ๋‚ฎ์Œ (์„ธ์…˜ ํด๋Ÿฌ์Šคํ„ฐ๋ง ํ•„์š”)๋งค์šฐ ๋†’์Œ (๋ถ„์‚ฐ ์„œ๋ฒ„ ํ™˜๊ฒฝ์— ์œ ๋ฆฌ)
๋ณด์•ˆ ์œ„ํ—˜์ฟ ํ‚ค ํƒˆ์ทจ(XSS)์„ธ์…˜ID ํƒˆ์ทจ (CSRF)ํ† ํฐ ์œ ์ถœ ์‹œ ์œ„ํ—˜ (Payload ์•ˆ์˜ ์ •๋ณด ๋…ธ์ถœ ๊ฐ€๋Šฅ)
๋งŒ๋ฃŒ ๊ด€๋ฆฌ์ฟ ํ‚ค ๋งŒ๋ฃŒ์ผ ์„ค์ •์„œ๋ฒ„์—์„œ ์„ธ์…˜ ๋งŒ๋ฃŒ ์ฒ˜๋ฆฌํ† ํฐ ๋งŒ๋ฃŒ ์‹œ๊ฐ„(exp) ์„ค์ • + Refresh Token ์‚ฌ์šฉ
์žฅ์ ๊ฐ„๋‹จํ•˜๊ณ  ๋น ๋ฆ„์œ ์ € ์ƒํƒœ ๊ด€๋ฆฌ ์šฉ์ด์„œ๋ฒ„ ํ™•์žฅ์— ์œ ๋ฆฌ, ์ƒํƒœ ์ €์žฅ ๋ถˆํ•„์š”
๋‹จ์ ๋ณด์•ˆ์— ์ทจ์•ฝ์„œ๋ฒ„ ์ž์› ์‚ฌ์šฉ ๋งŽ์Œํ† ํฐ ํƒˆ์ทจ ์‹œ ์œ„ํ—˜, ํ† ํฐ ๊ธธ์ด๊ฐ€ ๊ธธ์–ด์ง




โœ๏ธ ํ•œ ์ค„ ํšŒ๊ณ 

์ธ์ฆ๊ณผ ์ธ๊ฐ€์— ๋Œ€ํ•œ ์ฐจ์ด๋ฅผ ์ •๋ฆฌํ•˜๊ณ , JWT๋ฅผ ํ™œ์šฉํ•œ ๋กœ๊ทธ์ธ ๊ตฌํ˜„๊ณผ ๋ณด์•ˆ ์š”์†Œ๊นŒ์ง€ ๊ณ ๋ คํ•œ ์ฝ”๋“œ๋ฅผ ์งœ๋ฉด์„œ, ๋ณด์•ˆ์— ๋Œ€ํ•ด ๋” ๊ณต๋ถ€ํ•ด์•ผ๊ฒ ๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค.

profile
๐ŸŒฑ๊ฐœ๋ฐœ ๊ธฐ๋ก์žฅ

0๊ฐœ์˜ ๋Œ“๊ธ€