Cookie์™€ ๋กœ๊ทธ์ธ ๐Ÿช(HttpOnly์™€ Secure Cookie)

yiwoojungยท2023๋…„ 8์›” 26์ผ
6

๋„คํŠธ์›Œํฌ

๋ชฉ๋ก ๋ณด๊ธฐ
1/1

Cookie๋ž€? ๐Ÿช

์„œ๋ฒ„๊ฐ€ ์‚ฌ์šฉ์ž์˜ ์›น ๋ธŒ๋ผ์šฐ์ €์— ์ „์†กํ•˜๋Š” ์ž‘์€ ๋ฐ์ดํ„ฐ ์กฐ๊ฐ

  • ๋ธŒ๋ผ์šฐ์ €๋Š” ๊ทธ ๋ฐ์ดํ„ฐ ์กฐ๊ฐ๋“ค์„ ์ €์žฅํ•ด ๋†“์•˜๋‹ค๊ฐ€, ๋™์ผํ•œ ์„œ๋ฒ„์— ์žฌ์š”์ฒญ ์‹œ ์ €์žฅ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ํ•จ๊ป˜ ์ „์†กํ•œ๋‹ค.
  • ์ฟ ํ‚ค๋Š” ๋‘ ์š”์ฒญ์ด ๋™์ผํ•œ ๋ธŒ๋Ÿฌ์šฐ์ €์—์„œ ๋“ค์–ด์™”๋Š”์ง€ ์•„๋‹Œ์ง€๋ฅผ ํŒ๋‹จํ•  ๋•Œ ์ฃผ๋กœ ์‚ฌ์šฉํ•œ๋‹ค.
    • ์ด๋ฅผ ์ด์šฉํ•˜๋ฉด ์‚ฌ์šฉ์ž์˜ ๋กœ๊ทธ์ธ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.
    • ์ƒํƒœ๊ฐ€ ์—†๋Š” http ํ”„๋กœํ† ์ฝœ์—์„œ ์ƒํƒœ ์ •๋ณด๋ฅผ ๊ธฐ์–ต์‹œ์ผœ ์ฃผ๊ธฐ ๋•Œ๋ฌธ

์ฟ ํ‚ค์˜ ๋ชฉ์ 

  1. ์„ธ์…˜ ๊ด€๋ฆฌ
    • ์„œ๋ฒ„์— ์ €์žฅํ•ด์•ผ ํ•  ๋กœ๊ทธ์ธ, ์žฅ๋ฐ”๊ตฌ๋‹ˆ ๋“ฑ์˜ ์ •๋ณด ๊ด€๋ฆฌ
  2. ๊ฐœ์ธํ™”
    • ์‚ฌ์šฉ์ž ์„ ํ˜ธ, ํ…Œ๋งˆ ๋“ฑ์˜ ์„ธํŒ…
  3. ํŠธ๋ž˜ํ‚น
    • ์‚ฌ์šฉ์ž์˜ ํ–‰๋™์„ ๊ธฐ๋กํ•˜๊ณ  ๋ถ„์„ํ•˜๋Š” ์šฉ๋„

ํŠน์ง•

  • http ํ†ต์‹ ์„ ํ•œ๋‹ค๋ฉด ์ฟ ํ‚ค๋ฅผ ์ฃผ๊ณ  ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.
  • ์„œ๋ฒ„์—์„œ๋„, ํด๋ผ์ด์–ธํŠธ์—์„œ๋„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ํด๋ผ์ด์–ธํŠธ์—์„œ๋„ ์ฟ ํ‚ค์— ์ ‘๊ทผํ•˜๊ณ  ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.
  • http์™€ https ์‚ฌ์ด์—๋„ ์ฟ ํ‚ค๋ฅผ ๊ตํ™˜ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ๊ฐ™์€ ๋„๋ฉ”์ธ์ด๋ผ๋ฉด ์„œ๋กœ ๋‹ค๋ฅธ scheme์ผ์ง€๋ผ๋„ ์ฟ ํ‚ค๋ฅผ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ๋‹ค.

์ฟ ํ‚ค ๋งŒ๋“ค๊ธฐ

http ์š”์ฒญ์„ ์ˆ˜์‹ ํ•  ๋•Œ, ์„œ๋ฒ„๋Š” ์‘๋‹ต๊ณผ ํ•จ๊ป˜ Set-Cookie ํ—ค๋”๋ฅผ ์ „์†กํ•  ์ˆ˜ ์žˆ๋‹ค.

  • ์ฟ ํ‚ค๋Š” ๋ณดํ†ต ๋ธŒ๋ผ์šฐ์ €์— ์˜ํ•ด ์ €์žฅ๋œ๋‹ค.
  • ์ฟ ํ‚ค๋Š” ๊ฐ™์€ ์„œ๋ฒ„์— ์˜ํ•ด ๋งŒ๋“ค์–ด์ง„ ์š”์ฒญ๋“ค์˜ cookie http header ์•ˆ์— ํฌํ•จ๋˜์–ด ์ „์†ก๋œ๋‹ค.
  • ๋งŒ๋ฃŒ์ผ๊ณผ ์ง€์†์‹œ๊ฐ„์„ ๋ช…์‹œํ•  ์ˆ˜ ์žˆ๋‹ค.
    • ๋งŒ๋ฃŒ๋œ ์ฟ ํ‚ค๋Š” ๋” ์ด์ƒ ๋ณด๋‚ด์ง€์ง€ ์•Š๋Š”๋‹ค.
  • ํŠน์ • ๋„๋ฉ”์ธ ํ˜น์€ ๊ฒฝ๋กœ๋ฅผ ์ œํ•œํ•  ์ˆ˜ ์žˆ๋‹ค.
Set-Cookie: <cookie-name>=<cookie-value>
# ์„œ๋ฒ„ ํ—ค๋”
HTTP/1.0 200 OK
Content-type: text/html
Set-Cookie: yummy_cookie=choco
Set-Cookie: tasty_cookie=strawberry
# ๋ธŒ๋ผ์šฐ์ €
GET /sample_page.html HTTP/1.1
Host: www.example.org
Cookie: yummy_cookie=choco; tasty_cookie=strawberry

์„ธ์…˜ ์ฟ ํ‚ค

  • ํ˜„์žฌ ์„ธ์…˜์ด ๋๋‚  ๋•Œ ์‚ญ์ œ
  • ๋ธŒ๋ผ์šฐ์ €๋Š” ํ˜„์žฌ ์„ธ์…˜์ด ๋๋‚˜๋Š” ์‹œ์ ์„ ์ •์˜
    • ์žฌ์‹œ์ž‘ํ•  ๋•Œ ์„ธ์…˜์„ ๋ณต์›ํ•ด ์„ธ์…˜ ์ฟ ํ‚ค๊ฐ€ ๋ฌด๊ธฐํ•œ ์กด์žฌํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๊ธฐ๋„ ํ•จ

์˜์†์ ์ธ ์ฟ ํ‚ค

  • Expires ์†์„ฑ์— ๋ช…์‹œ๋œ ๋‚ ์งœ์— ์‚ญ์ œ๋˜๊ฑฐ๋‚˜
  • Max-Age ์†์„ฑ์— ๋ช…์‹œ๋„๋‹ˆ ๊ธฐ๊ฐ„ ์ดํ›„์— ์‚ญ์ œ

Secure Cookie์™€ HttpOnly

  • HTTP Only Cookie๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด client์—์„œ javascript๋ฅผ ํ†ตํ•œ ์ฟ ํ‚ค ํƒˆ์ทจ๋ฌธ์ œ(cross-site scripting XSS)๋ฅผ ์˜ˆ๋ฐฉํ•  ์ˆ˜ ์žˆ๋‹ค.
    Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; **HttpOnly**
    • ์„œ๋ฒ„ ์ชฝ์—์„œ ์ง€์†๋˜๊ณ  ์žˆ๋Š” ์„ธ์…˜์˜ ์ฟ ํ‚ค๋Š” Javascript๋ฅผ ์‚ฌ์šฉํ•  ํ•„์š”๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์— httpOnlyํ”Œ๋ž˜๊ทธ๊ฐ€ ์„ค์ •๋  ๊ฒƒ์ด๋‹ค.
    • javascript๊ฐ€ ์•„๋‹Œ ๋„คํŠธ์›Œํฌ๋ฅผ ์ง์ ‘ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜์—ฌ ์ฟ ํ‚ค๋ฅผ ๊ฐ€๋กœ์ฑŒ ์ˆ˜ ๋„ ์žˆ๋‹ค.
  • ์ด๋Ÿฐ ์ •๋ณด์œ ์ถœ์„ ๋ง‰๊ธฐ ์œ„ํ•ด HTTPS ํ”„๋กœํ† ์ฝœ์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ์•”ํ˜ธํ™”ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์ฃผ๋กœ ์‚ฌ์šฉ๋˜๊ณ  ์žˆ๋‹ค.
    • ๋ธŒ๋ผ์šฐ์ €๋Š” http://๋กœ ์‹œ์ž‘๋˜๋Š” ์ฝ”๋“œ๋ฅผ ๋งŒ๋‚˜๋ฉด ์•”ํ˜ธํ™”๋˜์ง€ ์•Š์€ ์ƒํƒœ๋กœ ์ฟ ํ‚ค๋ฅผ ์„œ๋ฒ„๋กœ ์ „๋‹ฌํ•œ๋‹ค.
      <img src="http://www.example.com/images/logo.png" />
  • ์ฟ ํ‚ค๋ฅผ ์ƒ์„ฑํ•  ๋•Œ secure ์ ‘๋ฏธ์‚ฌ๋ฅผ ์‚ฌ์šฉ
    • https๊ฐ€ ์•„๋‹Œ ํ†ต์‹ ์—์„œ๋Š” ์ฟ ํ‚ค๋ฅผ ์ „์†กํ•˜์ง€ ์•Š๋Š”๋‹ค.

      Set-Cookie: ์ฟ ํ‚ค๋ช…=์ฟ ํ‚ค๊ฐ’; path=/; **secure**

JWT

JWT ํ† ํฐ(JSON Web Token)์€ header, payload, signature 3๊ฐ€์ง€๊ฐ€ .(dot)๋ฅผ ๊ตฌ๋ถ„์ž๋กœ ํ•˜์—ฌ ์ด๋ฃฌ๋‹ค.

์•”ํ˜ธํ™”๋‚˜ ์‹œ๊ทธ๋‹ˆ์ฒ˜ ์ถ”๊ฐ€๊ฐ€ ๊ฐ€๋Šฅํ•œ ๋ฐ์ดํ„ฐํŒจํ‚ค์ง€(jwt).


๋กœ๊ทธ์ธ ๊ตฌํ˜„ํ•˜๊ธฐ

https://velog.io/@yaytomato/ํ”„๋ก ํŠธ์—์„œ-์•ˆ์ „ํ•˜๊ฒŒ-๋กœ๊ทธ์ธ-์ฒ˜๋ฆฌํ•˜๊ธฐ

๋กœ๊ทธ์ธ ์ธ์ฆ ๋ฐฉ์‹

  1. ์„ธ์…˜ id๋ฅผ ์ด์šฉํ•˜๋Š” ๋ฐฉ์‹
    1. ํด๋ผ์ด์–ด์–ธํŠธ ๋กœ๊ทธ์ธ โ‡’ ์„œ๋ฒ„ ์„ธ์…˜ ์ƒ์„ฑ, ์„ธ์…˜์˜ id ์ „์†ก โ‡’ ํด๋ผ์ด์–ธํŠธ์— ์ €์žฅ โ‡’ ์ธ์ฆ์ด ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ๋•Œ ์„œ๋ฒ„์— id ์ „์†ก โ‡’ ์„œ๋ฒ„๋Š” ์„ธ์…˜์ด ์œ ํšจํ•œ์ง€ ํ™•์ธ
  2. JWT๋ฅผ ์ด์šฉํ•˜๋Š” ๋ฐฉ์‹
    1. accessToken, refreshToken์„ ์‚ฌ์šฉ
    2. ํด๋ผ์ด์–ธํŠธ ๋กœ๊ทธ์ธ โ‡’ ์„œ๋ฒ„๊ฐ€ ์ธ์ฆ์ •๋ณด(JWT์•ˆ์— ์ธ์ฆ ์ •๋ณด๋ฅผ ๋‹ด์•„์„œ)๋ฅผ ์ „์†ก โ‡’ ์ธ์ฆ ์ •๋ณด ์ค‘ accessToken ๊ณผ refreshToken์„ ํด๋ผ์ด์–ธํŠธ์— ์ €์žฅ โ‡’ accessToken ์„ ๋กœ๊ทธ์ธํ•œ ์œ ์ €์—๊ฒŒ๋งŒ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ๋Š” ์ •๋ณด์— ์ ‘๊ทผํ•  ๋•Œ ์„œ๋ฒ„์— ์ „์†ก โ‡’ ํ† ํฐ์ด ์œ ํšจํ•œ์ง€ ํ™•์ธ
      1. ์‹ค์งˆ์ ์ธ ์ธ์ฆ์ •๋ณด๋Š” accessToken ์ธ๋ฐ ์ผ์ •์‹œ๊ฐ„์ด ์ง€๋‚˜๋ฉด ๋งŒ๋ฃŒ
        • ๋กœ์ปฌ ๋ณ€์ˆ˜์— ์ €์žฅ โ‡’ api ์š”์ฒญํ•  ๋•Œ authorization header์— ๋„ฃ์–ด์„œ ๋ณด๋‚ด์ค€๋‹ค.
      2. ์ด๋•Œ refreshToken์„ ์ด์šฉํ•ด ๋กœ๊ทธ์ธ์„ ์ง€์†์ ์œผ๋กœ ์œ ์ง€
        • refreshToken์„ ์„œ๋ฒ„์— ์ „์†ก โ‡’ ์„œ๋ฒ„๋Š” ์ƒˆ๋กœ์šด accessToken ์„ ๋ฐœ๊ธ‰

๋ณด์•ˆ

  • XSS
    • ํด๋ผ์ด์–ธํŠธ ๋ธŒ๋ผ์šฐ์ €์— js๋ฅผ ์‚ฝ์••ํ•ด ์‹คํ–‰ํ•˜๋Š” ๊ณต๊ฒฉ
      • ๊ณต๊ฒฉ์ž์˜ ์ฝ”๋“œ๊ฐ€ ๋‚ด ์‚ฌ์ดํŠธ์˜ ๋กœ์ง์ธ ์ฒ™ ํ–‰๋™ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • CSRF
    • ๋‹ค๋ฅธ ์‚ฌ์ดํŠธ์—์„œ ์šฐ๋ฆฌ ์‚ฌ์ดํŠธ์˜ api ์ฝœ์„ ์š”์ฒญํ•ด ์‹คํ–‰ํ•˜๋Š” ๊ณต๊ฒฉ
      • ๋กœ๊ทธ์ธํ•œ ์ฒ™ ๊ณ„์ขŒ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋ฐ”๊พธ๊ฑฐ๊ฐ€ ์†ก๊ธˆ์„ ๋ณด๋‚ธ๋‹ค.
  • ์ธ์ฆ์ •๋ณด๋ฅผ localStorage๋‚˜ cookie์— ์ €์žฅํ•˜๋Š” ๋ฐฉ์‹์€ ๋ณด์•ˆ์— ์ทจ์•ฝํ•˜๋‹ค.

๋ฆฌ์•กํŠธ์— ์ ์šฉํ•˜๊ธฐ

  1. secure ์ฟ ํ‚ค ์ „๋‹ฌ์„ ์œ„ํ•ด์„œ ํ”„๋ก ํŠธ์™€ ๋กœ๊ทธ์ธ api๋ฅผ ์ œ๊ณตํ•  ๋ฐฑ์—”๋“œ๋Š” ๊ฐ™์€ ๋„๋ฉ”์ธ์„ ๊ณต์œ ํ•ด์•ผํ•œ๋‹ค.
  2. ๋ฐฑ์—”๋“œ๋Š” http ์‘๋‹ต Set-Cookieํ—ค๋”์— refreshToken ๊ฐ’์„ ์„ค์ •ํ•˜๊ณ  accessToken์„ JSON payload์— ๋‹ด์•„ ๋ณด๋‚ด์ค€๋‹ค.
  3. root์—์„œ axios์— withCredentials true ๋กœ ์„ค์ •ํ•œ๋‹ค.
    axios.defaults.baseURL = "https://www.abc.com";
    axios.defaults.withCredentials = true;
  1. ๋กœ๊ทธ์ธ api ์š”์ฒญ

    onLogin = (email, password) => {
    	const data = {
    		email,
    		password,
    	};
    	axios.post('/login', data).then(response => {
    		const { accessToken } = response.data;
    
    		// API ์š”์ฒญํ•˜๋Š” ์ฝœ๋งˆ๋‹ค ํ—ค๋”์— accessToken ๋‹ด์•„ ๋ณด๋‚ด๋„๋ก ์„ค์ •
    		axios.defaults.headers.common['Authorization'] = `Bearer ${accessToken}`;
    
    		// accessToken์„ localStorage, cookie ๋“ฑ์— ์ €์žฅํ•˜์ง€ ์•Š๋Š”๋‹ค!
    
    	}).catch(error => {
    		// ... ์—๋Ÿฌ ์ฒ˜๋ฆฌ
    	});
    }
  2. ๋กœ๊ทธ์ธ ๋งŒ๋ฃŒ, ๋กœ๊ทธ์ธ ์—ฐ์žฅ ์ฒ˜๋ฆฌ

    ์œ ์ €๊ฐ€ ๋ชจ๋ฅด๊ฒŒ ์„œ๋ฒ„์—์„œ ์ƒˆ๋กœ์šด accessToken์„ ๋ฐ›์•„์™€์„œ ์กฐ์šฉํžˆ ์ž๋™์œผ๋กœ ๋กœ๊ทธ์ธ์ด ์—ฐ์žฅ๋˜๋„๋ก ํ•  ์ˆ˜ ์žˆ๋‹ค. (Silent Refresh)

  1. ์ผ๋ฐ˜์ ์ธ ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ
  2. accssToken์ด ๋งŒ๋ฃŒ๋์„ ๋•Œ ๋กœ๊ทธ์ธ ์—ฐ์žฅ ์ฒ˜๋ฆฌ
      - 401 error
  3. ํŽ˜์ด์ง€ reload ๋  ๋•Œ ๋กœ๊ทธ์ธ ์—ฐ์žฅ ์ฒ˜๋ฆฌ


Reference

profile
ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์ž

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

comment-user-thumbnail
2024๋…„ 1์›” 22์ผ

์•ˆ๋…•ํ•˜์„ธ์š”! ์ข‹์€ ๊ธ€ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค:)
๊ถ๊ธˆํ•œ๊ฒŒ ์žˆ๋Š”๋ฐ์š”..
axios.defaults.headers.common['Authorization'] = Bearer ${accessToken};
์— ํ† ํฐ ์ €์žฅํ•ด๋‘๋ฉด ์ƒˆ๋กœ๊ณ ์นจํ•ด๋„ ์•ˆ๋‚ ๋ผ๊ฐ€๋‚˜์š”?

๋‹ต๊ธ€ ๋‹ฌ๊ธฐ