์ด์ ๋ถํฐ๋ ์น ๋ณด์์ ๊ฐ์ฅ ํต์ฌ์ ์ธ ๋ ๊ฐ์ง ์ฃผ์ , CORS์ CSRF๋ฅผ ๋ค๋ฃฌ๋ค.
์ด ๋์ ํ๋ ์์ํฌ๋ฅผ ๋ ๋ ๋ชจ๋ ์น ์ ํ๋ฆฌ์ผ์ด์ ์์ ๋ฐ์ํ ์ ์๋ ์ทจ์ฝ์ ์ด๋ฉฐ,
Spring Security์ ๊ธฐ๋ณธ ์๋ฆฌ๋ฅผ ์ ๋๋ก ์ดํดํ๊ธฐ ์ํด์๋ ํ์ ๊ฐ๋ ์ด๋ค.
์ด๋ฒ ํํธ์์๋
๋ธ๋ผ์ฐ์ ๋ ์๋ก ๋ค๋ฅธ ์ถ์ฒ(origin) ๊ฐ์ ๋ฆฌ์์ค ์ ๊ทผ์ ๊ธฐ๋ณธ์ ์ผ๋ก ์ฐจ๋จํ๋ค.
์ด ์ ์ฑ ์ด ๋ฐ๋ก Same-Origin Policy(SOP)์ด๋ฉฐ,
CORS๋ SOP์ ์์ธ๋ฅผ ์์ ํ๊ฒ ์ด์ด์ฃผ๋ ๋ช ์์ ํ์ฉ ๊ท์น์ด๋ค.
| ๊ตฌ์ฑ ์์ | ์์ |
|---|---|
| ํ๋กํ ์ฝ | http / https |
| ๋๋ฉ์ธ | localhost / example.com |
| ํฌํธ๋ฒํธ | 8080 / 4200 |
์ฆ, ๋ค์์ ์๋ก ๋ค๋ฅธ ์ถ์ฒ์ด๋ค.
http://localhost:8080 โ http://localhost:4200
์ด๋ ๋ฐฑ์๋๊ฐ ๋ค์๊ณผ ๊ฐ์ ํค๋๋ฅผ ์๋ต์ผ๋ก ๋ฐํํ๋ฉด, ๋ธ๋ผ์ฐ์ ๋ ์์ฒญ์ ํ์ฉํ๋ค.
Access-Control-Allow-Origin: http://localhost:4200
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: Content-Type

http.cors(cors -> cors.configurationSource(request -> {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("http://localhost:4200"));
config.setAllowedMethods(List.of("*"));
config.setAllowedHeaders(List.of("*"));
config.setAllowCredentials(true);
config.setMaxAge(3600L);
return config;
}));
Access-Control-Allow-* ๊ฐ์ผ๋ก ์๋ ์ฃผ์
๋๋ค| ๊ตฌ๋ถ | ์ค๋ช |
|---|---|
โ *(๋ณํ) ๋จ์ฉ | ๊ฐ๋ฐ ํธ์๋ ๋์ง๋ง, ๋ณด์์ ์ํํจ |
| โ ๊ตฌ์ฒด์ Origin ๋ช ์ | ํ์ ์ถ์ฒ๋ง ํ์ฉ (localhost, ๋ฐฐํฌ ๋๋ฉ์ธ ๋ฑ) |
| โ preflight ์บ์ | maxAge๋ก ์ฑ๋ฅ ๊ฐ์ (๋ณดํต 3600์ด~24์๊ฐ) |
CSRF๋ ํด์ปค๊ฐ ์ฌ์ฉ์์ ์ธ์ ์ ์ ์ฉํ์ฌ,
์๋ํ์ง ์์ ์์ฒญ์ ์๋ฒ์ ๋ณด๋ด๋ ๊ณต๊ฒฉ์ด๋ค.
์ฆ, ํด์ปค๊ฐ ์ฌ์ฉ์ ๋์ โ์ ์ ์์ฒญโ์ ๋ณด๋ด๋๋ก ์์ด๋ ํ์์ด๋ค.

โก ๋ธ๋ผ์ฐ์ ๋ ๋์ผํ ์ธ์
์ฟ ํค(JSESSIONID)๋ฅผ ์ฒจ๋ถํ๊ธฐ ๋๋ฌธ์,
์๋ฒ๋ โ์ ์์ ์ธ ์์ฒญโ์ผ๋ก ์ค์ธํ๊ฒ ๋๋ค.
Spring Security๋ CSRF Token์ด๋ผ๋ 1ํ์ฉ ์ธ์ฆ ํ ํฐ์ ์์ฑํ์ฌ
์์ฒญ ๊ฒ์ฆ ์ โ์ฟ ํค ๊ฐ + ์์ฒญ ํค๋ ๊ฐโ์ ์ผ์น ์ฌ๋ถ๋ฅผ ํ์ธํ๋ค.
| ๊ตฌ๋ถ | ์ค๋ช |
|---|---|
| ์ฟ ํค | XSRF-TOKEN โ ๋ฐฑ์๋๊ฐ ์์ฑํด ํ๋ก ํธ์ ์ ๋ฌ |
| ์์ฒญ ํค๋ | X-XSRF-TOKEN โ ํ๋ก ํธ์์ ์ฟ ํค ๊ฐ์ ์ฝ์ด ํค๋์ ์ฝ์
|
| ๊ฒ์ฆ ๋ฐฉ์ | ๋ ๊ฐ์ด ์ผ์นํ๋ฉด ์์ฒญ ํ์ฉ, ๋ถ์ผ์น ์ 403 ๋ฐํ |

| ๊ตฌ์ฑ ์์ | ์ญํ |
|---|---|
CsrfToken ์ธํฐํ์ด์ค | ํ ํฐ์ ๊ตฌ์กฐ ์ ์ |
CookieCsrfTokenRepository | ์ฟ ํค ๊ธฐ๋ฐ ์ ์ฅ์ ๊ตฌํ |
CsrfFilter | ์์ฒญ ์ ํ ํฐ ๊ฒ์ฆ ์ํ |
OncePerRequestFilter | ์์ฒญ๋น 1ํ ํํฐ ์คํ |
CsrfTokenRequestAttributeHandler | ์์ฒญ ํค๋์ ํ ํฐ ๊ฐ์ ์ถ์ถ |
http.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler())
.ignoringRequestMatchers("/register", "/contact")
);
http.addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class);
withHttpOnlyFalse() : JS๊ฐ ์ฟ ํค ์ฝ์ ์ ์๋๋ก ์ค์ CsrfCookieFilter : ํ ํฐ์ ์ง์ฐ ์์ฑ ํ ์ฟ ํค ๋ ๋๋ง/register, /contact : ๊ณต์ฉ API๋ CSRF ๋ณดํธ ์ ์ธ// ๋ก๊ทธ์ธ ์ฑ๊ณต ์ ์ฟ ํค ์ฝ๊ธฐ
const xsrf = getCookie('XSRF-TOKEN');
sessionStorage.setItem('XSRF-TOKEN', xsrf!);
// ์์ฒญ ์ธํฐ์
ํฐ์์ ํค๋ ์ถ๊ฐ
if (xsrf) {
req = req.clone({
headers: req.headers.set('X-XSRF-TOKEN', xsrf),
withCredentials: true
});
}
getCookie() โ ์ฟ ํค ๊ฐ ์ถ์ถsessionStorage์ ๋ณด๊ดCSRF ๋ณดํธ์ ํจ๊ป ์์ฃผ ์ฐ์ด๋ ๋น๋ฐ๋ฒํธ ํด์ฑ๋ ์ง๊ณ ๋์ด๊ฐ์.
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String raw = "EazyBytes@12345";
String encoded = encoder.encode(raw);
System.out.println(encoded);
System.out.println(encoder.matches(raw, encoded)); // true
| ๊ตฌ๋ถ | ๋ด์ฉ |
|---|---|
| ์๊ณ ๋ฆฌ์ฆ | bcrypt (adaptive hash, salt ์๋ ์ถ๊ฐ) |
| ๊ฐ์ | ๋๋ฆฐ ๊ณ์ฐ์ผ๋ก ๋ฌด์ฐจ๋ณ ๋์ ๊ณต๊ฒฉ ๋ฐฉ์ด |
| Salt ์ ์ฅ | ํด์ ๋ด๋ถ์ ํฌํจ๋จ (๋น๊ต ์ ์๋ ์ถ์ถ) |

| ํญ๋ชฉ | CORS | CSRF |
|---|---|---|
| ๊ณต๊ฒฉ ๋์ | ๋ธ๋ผ์ฐ์ ์ ์ฑ | ์๋ฒ ์์ฒญ ์์กฐ |
| ์ฃผ์ฒด | ๋ธ๋ผ์ฐ์ ๋ณด์ ์ ์ฑ | ํด์ปค์ ์์กฐ ์์ฒญ |
| ์ฃผ์ ์์ธ | ๋ค๋ฅธ Origin ๊ฐ ์์ฒญ | ๋์ผ ์ฟ ํค์ ์๋ ์ฒจ๋ถ |
| ๋ฐฉ์ด ๋ฐฉ์ | Access-Control-Allow-Origin | CSRF Token (์ฟ ํค + ํค๋) |
| Spring Security ์ค์ | http.cors() | http.csrf() |
| ํ๋ก ํธ ์ฐธ์ฌ ํ์ | โ ์์ | โ ์ฟ ํค ์ฝ์ด ํค๋ ์ ์ก |
CORS๋ โ๋๊ฐ ๋์๊ฒ ์ ๊ทผํ ์ ์๋๊ฐโ,
CSRF๋ โ๋๊ฐ ๋๋ฅผ ๋์ ํด์ ํ๋ํ ์ ์๋๊ฐโ์ ๋ํ ๋ฌธ์ ๋ค.
์ค์ ์๋น์ค์์๋ ๋ ๊ฐ์ง๋ฅผ ๋์์ ๊ณ ๋ คํด์ผ ํ๋ค.
Spring Security๋ ์ด ๋ ๊ณ์ธต์ ๋ชจ๋ ์ ์ดํ ์ ์๋
๊ฐ์ฅ ๊ฐ๋ ฅํ ๋ณด์ ํ๋ ์์ํฌ ์ค ํ๋๋ค.