๋ธ๋ผ์ฐ์ ์ ์ฟ ํค ์ ์ฅ ์๋๋ ๋ฌธ์ ํด๊ฒฐ ๊ณผ์
๐ช๋ฌธ์ ์ํฉ
- ์กฐํ์ ์ค๋ณต ์ฆ๊ฐ ์ ์ด์ 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 ๋ผ๋๊ฒ๋ ๊ผญ ํ์ธํ์๊ตฌ์
- ์๋ฒ ๋ก๊ทธ์ ๊ฐ๋ฐ์ ๋๊ตฌ๋ฅผ ์ ์ฉํ์