์นดํ…Œ๊ณ ๋ฆฌ ์—†์Œ

๋ธŒ๋ผ์šฐ์ €์— ์ฟ ํ‚ค ์ €์žฅ ์•ˆ๋˜๋Š” ๋ฌธ์ œ ํ•ด๊ฒฐ ๊ณผ์ •

Seung__Yong 2024. 2. 23. 01:40

๐Ÿช๋ฌธ์ œ ์ƒํ™ฉ

  • ์กฐํšŒ์ˆ˜ ์ค‘๋ณต ์ฆ๊ฐ€ ์ œ์–ด์™€ Refresh Token๊ด€๋ฆฌ๋ฅผ ์œ„ํ•ด ์ฟ ํ‚ค๋ฅผ ๋„์ž…ํ–ˆ๋Š”๋ฐ ์ฟ ํ‚ค๊ฐ€ ์„œ๋ฒ„๋กœ ์ „๋‹ฌ์ด ์•ˆ๋˜๋Š” ๋ฌธ์ œ๋ฅผ ์ ‘ํ–ˆ์Šต๋‹ˆ๋‹ค.
  • ์ฒ˜์Œ์—๋Š” Local์ชฝ์—์„œ ํ†ต์‹ ์ด ์•ˆ๋˜๋Š” ๋ฌธ์ œ์˜€์œผ๋ฉฐ CORS์™€ withCredentials์„ค์ •์œผ๋กœ ์ธํ•œ ๋ฌธ์ œ์˜€์Šต๋‹ˆ๋‹ค.
  • ๋‹ค์Œ์œผ๋กœ๋Š” ์„œ๋ฒ„๋กœ ๋ฐฐํฌ ํ›„ ํ…Œ์ŠคํŠธ ํ•ด๋ดค๋Š”๋ฐ samesite์— ์˜ํ•ด ์ฟ ํ‚ค ์ „๋‹ฌ์ด ์•ˆ๋˜๋Š” ๋ฌธ์ œ์˜€์Šต๋‹ˆ๋‹ค.
  • ๋‹ค๋“ค ์ฟ ํ‚ค๋ฅผ ์†์— ๋„ฃ์–ด๋ด…์‹œ๋‹ค.

๐ŸชCORS์™€ withCredentials

CORS

์ €๋Š” 3000๋ฒˆ ํฌํŠธ์— React 8080ํฌํŠธ์— Spring์„ ๋„์›Œ๋‘” ์ƒํƒœ์˜€์Šต๋‹ˆ๋‹ค. 
ํฌํŠธ๊ฐ€ ๋‹ค๋ฅด๊ธฐ ๋•Œ๋ฌธ์— CORS์„ค์ •์„ ํ•ด์ฃผ์–ด์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค.

public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("http://localhost:3000")
                .allowedHeaders("*")
                .allowedMethods("*")
}

 

WithCredentials

์ด์ œ CORS๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜์ง„ ์•Š์•˜์ง€๋งŒ ์„œ๋ฒ„์—์„œ ์‘๋‹ต์ด ๋‚˜๊ฐ„ ์ฟ ํ‚ค๊ฐ€ ๋‹ค์Œ ์š”์ฒญ์— ์•ˆ๋‹ด๊ธฐ๋Š” ๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒผ์Šต๋‹ˆ๋‹ค.

์•„๋ž˜ ๊ธ€์— ์„ค๋ช…ํžˆ ๋„ˆ๋ฌด ์ž˜ ๋‚˜์™€์žˆ์–ด ๊ฐ„๋‹จํžˆ ์š”์•ฝํ•˜์ž๋ฉด ๋‹ค๋ฅธ ๋„๋ฉ”์ธ์— ์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ ์ฟ ํ‚ค์™€ ๊ฐ™์€ ์ธ์ฆ ๊ด€๋ จ ๋ฐ์ดํ„ฐ๋ฅผ ๋„˜๊ธฐ๋ ค๋ฉด ์„ค์ •ํ•ด์ค˜์•ผ ํ•˜๋Š” ์˜ต์…˜์ž…๋‹ˆ๋‹ค!!

๐Ÿช CORS ์ฟ ํ‚ค ์ „์†กํ•˜๊ธฐ (withCredentials ์˜ต์…˜) (tistory.com)

 

 

์ฃผ์˜ํ•ด์•ผ ํ•  ์ ์€ ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„ ์ธก์— ๋ชจ๋‘ ์„ค์ •์„ ํ•ด์ค˜์•ผํ•œ๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ๋Š” ์ƒ๋žตํ•˜๊ณ  ์„œ๋ฒ„ ์ชฝ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ์ˆ˜์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ allowCredentials(true)๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด allowedOrigins("*")์ด ์•„๋‹Œ ๋ช…์‹œ์ ์ธ url์„ ๋„ฃ์–ด์ค˜์•ผํ•ฉ๋‹ˆ๋‹ค.

public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("http://localhost:3000")
                .allowedHeaders("*")
                .allowedMethods("GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS" , "PATCH")
                .exposedHeaders("accessToken", "refreshToken")
                .allowCredentials(true);
}

 

 

๋ฐฐํฌ ํ›„ ๐Ÿช ์‚ฌ๋ผ์ง...

 

๋กœ์ปฌ์—์„œ๋Š” ๋ถ„๋ช…ํžˆ ์ž˜ ๋‹ด๊ธฐ๋˜ ์ฟ ํ‚ค๊ฐ€ ์žˆ์—ˆ๋Š”๋ฐ ์—†์–ด์กŒ๋„ค? ์™€์šฐ

์›์ธ์„ ๋ณด๋ฉด React์„œ๋ฒ„๋Š” front.vercel.app๋„๋ฉ”์ธ์„ ์‚ฌ์šฉํ•˜๊ณ  Spring์„œ๋ฒ„๋Š” sever.back.site์™€ ๊ฐ™์€ ๋„๋ฉ”์ธ์œผ๋กœ ์„ค์ •๋๊ธฐ ๋•Œ๋ฌธ์ด์—ˆ๋Š”๋ฐ์š”

 

์ฟ ํ‚ค์˜ ์˜ต์…˜์œผ๋กœ ๋“ค์–ด๊ฐ„ SameSite, Secure, httpOnly์ค‘ ํ•˜๋‚˜๊ฐ€ ์˜ํ–ฅ์„ ์คฌ์„๊ฑฐ๊ฐ™์€๋ฐ ์•„๋ž˜ ๋†ˆ๋“ค์€ ์—ฐ๊ด€์ด ์—†๋Š”๊ฑฐ ๊ฐ™์ฃ ..?

httpOnly : ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋กœ์˜ ์ ‘๊ทผ์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋„๋ก ์„ค์ •
secure : ์ฟ ํ‚ค๋ฅผ ๋ฐ˜๋“œ์‹œ https๋ฅผ ํ†ตํ•ด์„œ๋งŒ ๊ณต์œ ํ•˜๊ฒ ๋‹ค๋Š” ๋œป
-> ( ์žก์ง€์‹์ธ๋ฐ localhost์—์„œ๋Š” secure์ง€์ •ํ•ด๋„ http๋กœ ํ†ต์‹  ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค! )

 

samesite 

 samesite๋Š” ๋ณด์•ˆ์ƒ์˜ ๋ฌธ์ œ๋กœ ๋งŒ๋“ค์–ด์กŒ๋‹ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.(CSRF ๊ฐ™์€?)

๋ณด์•ˆ ์ •์ฑ…์€ ์—ฌ๋Ÿฌ ํฌ์ŠคํŒ…์—์„œ ๋˜‘๊ฐ™์€๋ง ๋Œ๋ ค๋Œ๋ ค ํ•˜๋Š”๋ฐ ์ œ๊ฐ€ ์ดํ•ดํ•˜๊ธฐ ์ข‹์•˜๋˜ ์„ค๋ช…์€ ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

Strict : ํผ์ŠคํŠธ ํŒŒํ‹ฐ ์ฟ ํ‚ค๋งŒ ์ „์†ก
Lax : ๋ช‡ ๊ฐ€์ง€ ์˜ˆ์™ธ์ ์ธ ์š”์ฒญ(์•ˆ์ „ํ•œ? HTTP์š”์ฒญ์ธ GET์š”์ฒญ)์„ ์ œ์™ธํ•˜๊ณ ๋Š” ํผ์ŠคํŠธ ํŒŒํ‹ฐ ์ฟ ํ‚ค๋งŒ ์ „์†ก
None : ํผ์ŠคํŠธ ํŒŒํ‹ฐ ์ฟ ํ‚ค์™€ ์„œ๋“œํŒŒํ‹ฐ ์ฟ ํ‚ค ์ „์†ก

 

ํผ์ŠคํŠธ ํŒŒํ‹ฐ ๐Ÿช ์„œ๋“œ ํŒŒํ‹ฐ ๐Ÿช

 

ํผ์ŠคํŠธ ํŒŒํ‹ฐ ์ฟ ํ‚ค๋Š” ํ˜„์žฌ ์ ‘์†ํ•ด ์žˆ๋Š” ํŽ˜์ด์ง€์™€ ๊ฐ™์€ ๋„๋ฉ”์ธ์œผ๋กœ ์ „์†ก๋˜๋Š” ์ฟ ํ‚ค์ด๋ฉฐ,

์„œ๋“œ ํŒŒํ‹ฐ ์ฟ ํ‚ค๋Š” ํ˜„์žฌ ์ ‘์†ํ•ด ์žˆ๋Š” ํŽ˜์ด์ง€์™€ ๋‹ค๋ฅธ ๋„๋ฉ”์ธ์œผ๋กœ ์ „์†ก๋˜๋Š” ์ฟ ํ‚ค์ž…๋‹ˆ๋‹ค.

 

์—ฌ๊ธฐ์„œ ๊ฐ™์€ ๋„๋ฉ”์ธ์˜ ๊ธฐ์ค€์„ ๋ญ˜๊นŒ์š”??

site๋Š” origin๊ณผ๋Š” ์กฐ๊ธˆ ๋‹ค๋ฅธ ๊ธฐ์ค€์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค. 

 

Top Level Domain  ->  (github.io, co.kr, com)

TLD ์•ž ๋„๋ฉ”์ธ์„ Resistrable Domain์ด๋ผ๊ณ  ํ•˜๊ณ 
set-Cookie Domain์˜ resistrable Domain๊ณผ ๋ธŒ๋ผ์šฐ์ € ์ฃผ์†Œ์ฐฝ URI์˜ registrable Domain์ด ์ผ์น˜ํ•  ๊ฒฝ์šฐ SameSite์ž…๋‹ˆ๋‹ค.

 

์˜ˆ์‹œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

Origin A Origin B Site Origin
https://www.back.site:443 https://server.back.site:443 ๊ฐ™์€ ๋„๋ฉ”์ธ ๋‹ค๋ฅธ ์„œ๋ธŒ๋„๋ฉ”์ธ
https://back.site:443 ๊ฐ™์€ ๋„๋ฉ”์ธ ๋‹ค๋ฅธ ์„œ๋ธŒ๋„๋ฉ”์ธ
https://www.back.site:80 ๊ฐ™์€ ๋„๋ฉ”์ธ ๋‹ค๋ฅธ ํฌํŠธ

 

ํ•ด๊ฒฐ๋ฐฉ๋ฒ•

์ €์˜ ๋ฌธ์ œ์ ์€ React์„œ๋ฒ„๋Š” front.vercel.app Spring์€ back.site ์ด๋Ÿฐ์‹์œผ๋กœ ๋‹ค๋ฅธ ๋„๋ฉ”์ธ์„ ์‚ฌ์šฉํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.

์ œ๊ฐ€ ์ฐพ์€ ์„ ํƒ์ง€๋Š” ๋‘ ๊ฐ€์ง€์˜€์œผ๋ฉฐ ์กฐ๊ธˆ ๋” ๋†’์€ ๋ณด์•ˆ์„ ์œ„ํ•ด ํ›„์ž๋ฅผ ์„ ํƒํ–ˆ์Šต๋‹ˆ๋‹ค.

 

1. ๋„๋ฉ”์ธ ๋ณ€๊ฒฝ์—†์ด sameSite : "None", secure : true์„ค์ • 

๋งŒ์•ฝ ๋„๋ฉ”์ธ์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์—†๋Š” ์ƒํ™ฉ์ด๋ผ๋ฉด ์ด ๋ฐฉ๋ฒ•์„ ์„ ํƒํ–ˆ์„ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ CSRS๋ฐฉ์–ด๊ฐ€ ์•ˆ๋  ์ˆ˜ ์žˆ๋Š” ๋ฌธ์ œ์ ์ด ์กด์žฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ฃผ์˜ํ•˜์—ฌ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

ResponseCookie cookie = ResponseCookie.from("viewCountCookie", randomCookieValue)
        .path("/")
        .sameSite("None")
        .httpOnly(true)
        .domain("back.site") // ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค! ์„œ๋ฒ„์˜ ๋„๋ฉ”์ธ๋งŒ ์ ์–ด์ฃผ๋ฉด ๋จ
        .secure(true) // sameSite๋ฅผ None์œผ๋กœ ์ง€์ •ํ–ˆ๋‹ค๋ฉด ํ•„์ˆ˜
        .build();

response.addHeader("Set-Cookie", cookie.toString());

 

 

2. ๋„๋ฉ”์ธ์„ ์ผ์น˜์‹œํ‚ค๊ณ  sameSite : "Strict", secure : true์„ค์ •

front.vercel.app์œผ๋กœ ์ง€์ •๋˜์–ด์žˆ๋˜ ํ”„๋ก ํŠธ ์„œ๋ฒ„๋ฅผ ๋ฐฑ ๋„๋ฉ”์ธ์˜ ์„œ๋ธŒ ๋„๋ฉ”์ธ์ธ back.site๋กœ ์˜ฎ๊ฒจ ์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

Lax๋Œ€์‹  Strict๋ฅผ ์“ด ์ด์œ ๋Š” CSRF๊ณต๊ฒฉ์„ ๋ง‰์•„ ๋ณด์•ˆ์„ฑ์„ ๋†’์ด๊ธฐ ์œ„ํ•จ์ด์—ˆ์Šต๋‹ˆ๋‹ค.

String randomCookieValue = UUID.randomUUID().toString();
ResponseCookie cookie = ResponseCookie.from("viewCountCookie", randomCookieValue)
        .path("/")
        .sameSite("Strict")
        .httpOnly(true)
        .domain("farmingsoon.site")
        .secure(true)
        .build();

response.addHeader("Set-Cookie", cookie.toString());

 

 

์ถ”๊ฐ€ ์‚ฌํ•ญ

  • ํ˜น์‹œ ์˜ˆ์ƒ๊ณผ ๋‹ค๋ฅธ ๋™์ž‘์œผ๋กœ ์‚ฝ์งˆ์„ ํ•˜๊ณ  ๊ณ„์‹ ๋‹ค๋ฉด ๊ผญ...์ฟ ํ‚ค์™€ ์บ์‹œ๋ฅผ ๋‚ ๋ ค๋ณด์„ธ์š” withcredectial sssss ๋ผ๋Š”๊ฒƒ๋„ ๊ผญ ํ™•์ธํ•˜์‹œ๊ตฌ์š” 
  • ์„œ๋ฒ„ ๋กœ๊ทธ์™€ ๊ฐœ๋ฐœ์ž ๋„๊ตฌ๋ฅผ ์• ์šฉํ•˜์ž