๐Ÿฆน๐Ÿปโ€โ™‚๏ธ CSRF์™€ ํ† ํฐ ๐Ÿง™๐Ÿปโ€โ™‚๏ธ

9rganizedChaosยท2021๋…„ 10์›” 10์ผ
2

์ธ์ฆ ๋ฟŒ์‹œ๊ธฐ! ๐Ÿ”

๋ชฉ๋ก ๋ณด๊ธฐ
3/4
post-thumbnail

CSRF (Cross Site Request Forgery)

๋‹ค๋ฅธ ์‚ฌ์ดํŠธ์—์„œ ์œ ์ €๊ฐ€ ๋ณด๋‚ด๋Š” ์š”์ฒญ์„ ์กฐ์ž‘ํ•˜๋Š” ๊ณต๊ฒฉ. ์˜ˆ์‹œ๋กœ๋Š” ์ด๋ฉ”์ผ์— ์ฒจ๋ถ€๋œ ๋งํฌ๋ฅผ ๋ˆ„๋ฅด๋ฉด ๋‚ด ์€ํ–‰๊ณ„์ขŒ์˜ ๋ˆ์ด ๋น ์ ธ๋‚˜๊ฐ€๋Š” ๋ฐฉ์‹์˜ ํ•ดํ‚น ๋“ฑ์ด ์žˆ๋‹ค.

ํ•ด์ปค๋Š” ๋ง๊ทธ๋Œ€๋กœ ๋‹ค๋ฅธ ์‚ฌ์ดํŠธ์— ์žˆ์œผ๋ฏ€๋กœ, ์‘๋‹ต ๊ฐ์ฒด์— ๋‹ด๊ธด ์ •๋ณด์ž์ฒด๋ฅผ ํƒˆ์ทจํ•  ์ˆ˜๋Š” ์—†์–ด์„œ, ์š”์ฒญ์„ ์กฐ์ž‘ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

CSRF ๊ณต๊ฒฉ์ด ์ด๋ฃจ์–ด์ง€๊ธฐ ์œ„ํ•œ ์กฐ๊ฑด

  • ์ฟ ํ‚ค๋ฅผ ์‚ฌ์šฉํ•œ ๋กœ๊ทธ์ธ: ์œ ์ €๊ฐ€ ๋กœ๊ทธ์ธ ํ–ˆ์„ ๋•Œ, ์ฟ ํ‚ค๋กœ ์–ด๋–ค ์œ ์ €์ธ์ง€ ์•Œ ์ˆ˜ ์žˆ์–ด์•ผ ํ•จ
  • ์˜ˆ์ธตํ•  ์ˆ˜ ์žˆ๋Š” ์š”์ฒญ/parameter๋ฅผ ๊ฐ–๊ณ  ์žˆ์–ด์•ผ ํ•จ: ๋ฆฌํ€˜์ŠคํŠธ์— ํ•ด์ปค๊ฐ€ ๋ชจ๋ฅผ ์ˆ˜ ์žˆ๋Š” ์ •๋ณด๊ฐ€ ๋‹ด๊ฒจ์žˆ์œผ๋ฉด ์•ˆ ๋จ. ์˜ˆ๋ฅผ ๋“ค์–ด ์€ํ–‰์—์„œ ๋ˆ์„ ๋นผ๋‚ด๋ ค๊ณ  ํ•  ๋•Œ, ์ด๋ฏธ ๋กœ๊ทธ์ธ ๋˜์–ด ์žˆ์–ด์„œ ์ฒ˜์Œ์€ ๋กœ๊ทธ์ธํ•  ํ•„์š”๊ฐ€ ์—†์ง€๋งŒ, ๋‚˜์ค‘์— ๋กœ๊ทธ์ธ์„ ๋˜ ์š”๊ตฌํ•˜๋ฉด ํ•ด์ปค๋Š” ๋กœ๊ทธ์ธ ๋ชป ํ•จ!

CSRF ๊ณผ์ •

  • ์œ ์ €๊ฐ€ ์€ํ–‰ ์›น์‚ฌ์ดํŠธ์— ๋กœ๊ทธ์ธ์„ ํ•œ๋‹ค -> ์„ธ์…˜์ด ํ™œ์„ฑํ™”๋œ๋‹ค. ๋กœ๊ทธ์ธ ์ •๋ณด๊ฐ€ ์ฟ ํ‚ค์— ๋‹ด๊ธด๋‹ค.
  • ์ด ๋•Œ ํ•ด์ปค๊ฐ€ ๊ฒŸ์š”์ฒญ์„ ํ™œ์„ฑํ™”ํ•˜๋Š” ๋งํฌ๋ฅผ ์œ ์ €์—๊ฒŒ ์ „์†กํ•œ๋‹ค.
  • ์œ ์ €๊ฐ€ ํ•ด๋‹น ๋งํฌ๋ฅผ ํด๋ฆญํ•˜๋ฉด ์˜๋„ํ•˜์ง€ ์•Š์€ ๊ฒŸ์š”์ฒญ์ด ์€ํ–‰ ์›น์‚ฌ์ดํŠธ๋กœ ๋ณด๋‚ด์ง„๋‹ค.

CSRF๋Š” ์–ด๋–ป๊ฒŒ ๋ง‰์„ ์ˆ˜ ์žˆ์„๊นŒ?

  • CSRF ํ† ํฐ ์‚ฌ์šฉํ•˜๊ธฐ: ์„œ๋ฒ„์ธก์—์„œ CSRF ๊ณต๊ฒฉ์— ๋ณดํ˜ธํ•˜๊ธฐ ์œ„ํ•œ ๋ฌธ์ž์—ด์„ ์œ ์ €์˜ ๋ธŒ๋ผ์šฐ์ €์™€ ์›น์•ฑ์—๋งŒ ์ œ๊ณต
  • Same-site cookie ์‚ฌ์šฉํ•˜๊ธฐ: ๊ฐ™์€ ๋„๋ฉ”์ธ์—์„œ๋งŒ ์„ธ์…˜/์ฟ ํ‚ค๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

ํ† ํฐ๊ธฐ๋ฐ˜ ์ธ์ฆ ์ ˆ์ฐจ (JWT)

์„ธ์…˜ ๊ธฐ๋ฐ˜ ์ธ์ฆ์€ ์„œ๋ฒ„์— ๋ถ€๋‹ด์„ ์ค€๋‹ค. ๋งค๋ฒˆ ์ธ์ฆ์„ ํ•  ๋•Œ๋งˆ๋‹ค DB๋ฅผ ์‚ดํŽด์•ผํ•˜๊ณ , ์šฐ์„  ๋ฐ์ดํ„ฐ ์–‘ ์ž์ฒด๊ฐ€ ์ฃผ๋Š” ๋ถ€๋‹ด๋„ ์กด์žฌํ•œ๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์ด ๋ถ€๋‹ด์„ ๋„˜๊ฒจ์ค„ ์ˆ˜๋Š” ์—†์„๊นŒ, ๊ณ ๋ฏผํ•  ๊ฒƒ์ด๋‹ค. ๊ทธ๋ ‡๊ฒŒ ๊ณ ์•ˆ๋œ ๊ฒƒ์ด ๋ฐ”๋กœ ํ† ํฐ ๊ธฐ๋ฐ˜ ์ธ์ฆ์ด๋‹ค!

์ธ์ฆ์˜ ํ•ต์‹ฌ์„ ์–ด๋–ป๊ฒŒ ํด๋ผ์ด์–ธํŠธ์— ๋ณด๊ด€ํ•  ์ˆ˜ ์žˆ์ง€? ํ•˜๋Š” ๊ณ ๋ฏผ์ด ํ‹€ ์ˆ˜ ์žˆ์ง€๋งŒ, ํ† ํฐ์€ ์•”ํ˜ธํ™”๋˜์–ด ์žˆ๋‹ค๋Š” ์ ์„ ๊ธฐ์–ตํ•ด์•ผ ํ•œ๋‹ค.

ํ† ํฐ์˜ ๊ตฌ์กฐ๋ฅผ ์‚ดํŽด๋ณด์ž.

ํ† ํฐ์˜ ๊ตฌ์กฐ!

๊ฐ€์žฅ ๋ฒ”์šฉ์ ์œผ๋กœ ์‚ฌ์šฉ๋˜๋Š” ํ† ํฐ์ธ jwt ๊ณต์‹ ํŽ˜์ด์ง€์— ๋“ค์–ด๊ฐ€๋ณด๋ฉด ๋ฉ”์ธํŽ˜์ด์ง€์—์„œ ๋ถ€ํ„ฐ ์œ„์™€ ๊ฐ™์€ ํ™”๋ฉด์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.
์ด ๋ถ€๋ถ„์ด ๋ฐ”๋กœ ํ† ํฐ์˜ ๊ตฌ์กฐ๋ฅผ ์„ค๋ช…ํ•˜๊ณ  ์žˆ๋‹ค.

  • Header: ์–ด๋–ค ์ข…๋ฅ˜์˜ ํ† ํฐ์ธ์ง€, ์–ด๋–ค ์•Œ๊ณ ๋ฆฌ์ฆ˜์œผ๋กœ ์•”ํ˜ธํ™”ํ•  ๊ฒƒ์ธ์ง€
  • Payload: ์–ด๋–ค ์ •๋ณด์— ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ์ง€์— ๋Œ€ํ•œ ๊ถŒํ•œ, ์‚ฌ์šฉ์ž์˜ ์œ ์ €๋„ค์ž„ ๋“ฑ (์•”ํ˜ธํ™”๊ฐ€ ๋˜๊ธด ํ•˜์ง€๋งŒ ๋ฏผ๊ฐํ•œ ์ •๋ณด๋Š” ๋‹ด์ง€ ์•Š๋Š” ํŽธ์ด ์ข‹๋‹ค.)
  • Signature: Header์™€ Payload์— secret ๊ฐ’๊นŒ์ง€ ๋” ํ•ด ์•”ํ˜ธํ™”ํ•œ ๋ถ€๋ถ„

ํ† ํฐ์˜ ์ธ์ฆ ๋กœ์ง!

Step1: ๋กœ๊ทธ์ธ ์š”์ฒญ์„ ๋ณด๋‚ธ๋‹ค!

    axios
      .post("https://localhost:4000/login", {
        userId: this.state.userId,
        password: this.state.password,
      })

Step2: ์•„์ด๋””์™€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ํ™•์ธํ•˜๊ณ , ํ† ํฐ์„ ์ƒ์„ฑํ•œ๋‹ค.

// ์•„๋ž˜ ์ฝ”๋“œ์—์„œ userInfo๋Š” ํด๋ผ์ด์–ธํŠธ์—์„œ ๋ณด๋‚ธ ์•„์ด๋””์™€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋กœ DB์—์„œ ์กฐํšŒํ•œ ์ •๋ณด
    let userInfoWithoutPW = Object.assign(userInfo.dataValues);
    // ํŒจ์Šค์›Œ๋“œ๋Š” ์‚ญ์ œํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค. ๋ฏผ๊ฐํ•œ ์ •๋ณด์ด๋ฏ€๋กœ!
    delete userInfoWithoutPW.password;
    const accessToken = jwt.sign(userInfoWithoutPW, process.env.ACCESS_SECRET, {
      expiresIn: "1h",
    });
    const refreshToken = jwt.sign(
      userInfoWithoutPW,
      process.env.REFRESH_SECRET,
      {
        expiresIn: "7d",
      }
    );
    res.cookie("refreshToken", refreshToken);
    res.json({
      data: { accessToken },
      message: "ok",
    });
  • jwt.sign()๋ฉ”์†Œ๋“œ์— ์ฒซ ๋ฒˆ์งธ ์ธ์ž๋กœ ๋“ค์–ด๊ฐˆ ์ •๋ณด๋Š” ์ž์œ ๋กญ๊ฒŒ ๋„ฃ์–ด๋‘˜ ์ˆ˜ ์žˆ๋‹ค. accessToken๊ณผ refreshToken์— ๋™์ผํ•œ ์ •๋ณด๋ฅผ ๋„ฃ์„ ํ•„์š”๋„ ์—†๋‹ค. ๋ชจ์ชผ๋ก ์œ ์ €๋ฅผ ๊ตฌ๋ถ„ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ’์ด๋ฉด ๋œ๋‹ค.

Step3: ํด๋ผ์ด์–ธํŠธ๋Š” ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ๋ฐ›์€ ํ† ํฐ์„ ์ €์žฅํ•œ๋‹ค

 this.props.issueAccessToken(res.data.data.accessToken);
  • ์ €์žฅํ•˜๋Š” ์œ„์น˜๋Š” local storage, cookie, react์˜ state ๋“ฑ ๋‹ค์–‘ํ•˜๋‹ค.

Step4: ํด๋ผ์ด์–ธํŠธ๊ฐ€ HTTP ํ—ค๋”(authorization ํ—ค๋”)์— ํ† ํฐ์„ ๋‹ด์•„ ๋ณด๋‚ธ๋‹ค

    axios({
      method: "get",
      url: "https://localhost:4000/accesstokenrequest",
      headers: { authorization: "Bearer " + this.props.accessToken },
    }).then((res) => {
      this.setState(res.data.data.userInfo);
    });
  • ์ด ๋•Œ Bearer๋ฅผ ๋ถ™์—ฌ์ฃผ๋Š” ์ด์œ ๋Š” accessToken์œผ๋กœ bearer Token์„ ๋„˜๊ฒจ์ค€๋‹ค๋Š” ์˜๋ฏธ์ด๋‹ค. ์‚ฌ์‹ค ์•„์ง Bearer Token์ด ๋ฌด์—‡์ธ์ง€ ์ •ํ™•ํžˆ ์ดํ•ดํ•˜์ง€๋Š” ๋ชปํ–ˆ์œผ๋‚˜, Basic๊ณผ Bearer๋ผ๋Š” ํฌ๋งท์ด ์กด์žฌํ•˜๊ณ , Basic์€ ๋ณ„๋„์˜ key์—†์ด ๋ณตํ˜ธํ™”๊ฐ€ ๊ฐ€๋Šฅํ•œ ์•ˆ์ „ํ•˜์ง€ ์•Š์€ ๋ฐฉ์‹์ด๋ผ๋Š” ์ , ๊ทธ๋ฆฌ๊ณ  "๋ณดํ˜ธ๋œ ๋ฆฌ์†Œ์Šค์— ๋Œ€ํ•œ ์ ‘๊ทผ ๊ถŒํ•œ์„ ๋ถ€์—ฌ๋ฐ›๊ธฐ ์œ„ํ•ด ์ œ์‹œํ•˜๋Š” ์œ ์ผํ•œ ์ž‘์—…์ด ํ† ํฐ์„ ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ ๋ฟ"์ผ ๋•Œ ํ•ด๋‹น ํ† ํฐ์„ bearer ํ† ํฐ์ด๋ผ ํ•œ๋‹ค๋Š” ๊ฒƒ ์ •๋„ ์ดํ•ดํ–ˆ๋‹ค.

์„œ๋ฒ„๋Š” ํ† ํฐ์„ ํ•ด๋…ํ•ด ์ธ์ฆ๊ณผ์ •์„ ๊ฑฐ์นœ๋‹ค.

  if (!req.headers.authorization) {
    res.status(401).send({ data: null, message: "invalid access token" });
  } else {
    let token = req.headers.authorization.split("Bearer ")[1];
    let userInfo = jwt.verify(token, process.env.ACCESS_SECRET);
    delete userInfo.iat;
    res.status(200).send({ data: { userInfo }, message: "ok" });
  }

ํ† ํฐ ๊ธฐ๋ฐ˜ ์ธ์ฆ ์žฅ์ 

1) ๋ฌด์ƒํƒœ์„ฑ & ํ™•์žฅ์„ฑ: ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ธ์ฆ ์ •๋ณด๋ฅผ ๊ฐ–๊ณ  ์žˆ์œผ๋ฏ€๋กœ, ์„œ๋ฒ„๊ฐ€ ์—ฌ๋Ÿฌ ๊ฐœ์ธ ์„œ๋น„์Šค๋ผ ํ•˜๋”๋ผ๋„, ํ•˜๋‚˜์˜ ํ† ํฐ์œผ๋กœ ํ•ด๊ฒฐ ๊ฐ€๋Šฅํ•˜๋‹ค.

2) ์•ˆ์ •์„ฑ: ์•”ํ˜ธํ™” ํ‚ค๋ฅผ ๋…ธ์ถœํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.

3) ์–ด๋””์„œ๋‚˜ ์ƒ์„ฑ ๊ฐ€๋Šฅ: ํŠน์ •ํ•œ ์„œ๋ฒ„๊ฐ€ ๊ผญ ํ† ํฐ์„ ๋งŒ๋“ค ํ•„์š”๊ฐ€ ์—†๋‹ค.

4) ๊ถŒํ•œ ๋ถ€์—ฌ์— ์šฉ์ด: ํ† ํฐ์˜ payload ์•ˆ์— ์–ด๋–ค ์ •๋ณด์— ๋Œ€ํ•œ ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•œ์ง€ ์ž„์˜๋กœ ์ •์˜๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค. (๋ถ€๋ถ„์  ์ ‘๊ทผ๊ถŒํ•œ์„ ์ค„ ์ˆ˜ ์žˆ๋‹ค)

Access Token & Refresh Token

token์ด ํƒˆ์ทจ๋  ๊ฒƒ์— ๋Œ€ํ•œ ์šฐ๋ ค๋กœ, token์„ ๋‘˜ ๋ฐœ๊ธ‰ํ•˜์—ฌ, access token๊ณผ refresh token์œผ๋กœ ์‚ฌ์šฉํ•œ๋‹ค. access token์—๋Š” ์งง์€ ์œ ํšจ๊ธฐ๊ฐ„์„ ์ค˜ ํƒˆ์ทจ๋˜๋”๋ผ๋„ ์˜ค๋žซ๋™์•ˆ ์‚ฌ์šฉ๋  ์ˆ˜ ์—†๋„๋กํ•˜๊ณ , access token์˜ ๊ธฐํ•œ๊ธฐ ๋งŒ๋ฃŒ๋˜๋ฉด, refresh token์œผ๋กœ ์ƒˆ access token์„ ๋ฐœ๊ธ‰๋ฐ›๋Š”๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ refresh token๋„ ํƒˆ์ทจ๋  ์ˆ˜ ์žˆ๊ณ , ๋ณด์•ˆ์ด ์—„์ฒญ ์ค‘์š”ํ•œ ์„œ๋น„์Šค๋ผ๋ฉด refresh token์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ๋„ ์žˆ๋‹ค๊ณ  ํ•œ๋‹ค! (๊ทธ๋Ÿผ ๊ธฐํ•œ ๋งŒ๋ฃŒ๋˜๋ฉด ๋งค๋ฒˆ ์ƒˆ๋กœ access token ๋ฐœ๊ธ‰๋ฐ›์Œ...?)

profile
๋ถ€์ •ํ™•ํ•œ ์ •๋ณด๋‚˜ ์ž˜๋ชป๋œ ์ •๋ณด๋Š” ๋Œ“๊ธ€๋กœ ์•Œ๋ ค์ฃผ์‹œ๋ฉด ๋น ๋ฅด๊ฒŒ ์ˆ˜์ •ํ† ๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค, ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค!

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