์คํ๋ง ๋ฐฑ์๋ ๊ฐ๋ฐ์๊ฐ CORS๋ฅผ ํ ์คํธ ํ๋ ๋ฐฉ๋ฒ
์์ ๊ฐ์ ์งค์ ๋ด์ฉ์ฒ๋ผ ํ๋ก ํธ์๋ ๊ฐ๋ฐ์์๊ฒ ๊ณ ํต์ ์ฃผ๋ ๊ฒ์ CORS ์ด๋ค. ๊ทธ๋ฐ๋ฐ CORS๋ ๋ธ๋ผ์ฐ์ ์์์ ์ ์ฑ ์์๋ ๋ถ๊ตฌํ๊ณ ํ๋ก ํธ์๋ ๊ฐ๋ฐ์๊ฐ ๋์ํ ์ ์๋ ๋ถ๋ถ์ ์์ผ๋ฉฐ ๋ธ๋ผ์ฐ์ ์์์ CORS ๋งค์ปค๋์ฆ์ ์ดํดํ๊ณ ์๋ฒ ๋ฐฑ์๋ ๊ฐ๋ฐ์๊ฐ ์ฒ๋ฆฌํด์ผํ ๋ถ๋ถ์ด๋ค. MDN์ Preflight Request ๋ฌธ์์์ ๋ค๋ฃจ๋ ๋ด์ฉ์ฒ๋ผ ๋๋ถ๋ถ์ CORS ๋ฌธ์ ๋ ํ๋ก ํธ์๋ ์ ํ๋ฆฌ์ผ์ด์ ์์์ XHR ์์ฒญ์ ์ํด ํ๋ฆฌํ๋ผ์ดํธ ์์ฒญ(Preflight Request)์ ์ํ CORS ์๋ฐฐ ์๋ต์ ๋ฐ๊ณ ๋ธ๋ผ์ฐ์ ์์ ์ ํํ๊ธฐ ๋๋ฌธ์ ๋ฐ์ํ๋ค.
ํ๋ฆฌํ๋ผ์ดํธ ์์ฒญ
ํ๋ฆฌํ๋ผ์ดํธ ์์ฒญ์ Origin
ํค๋์ Access-Control-Request-Method
ํค๋ ๊ทธ๋ฆฌ๊ณ OPTIONS
๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ์ํ๋๋ค.
HTTPOPTIONS /resource/foo Access-Control-Request-Method: DELETE Access-Control-Request-Headers: origin, x-requested-with Origin: https://foo.bar.org
์ฐธ๊ณ ๋ก, ์คํ๋ง ํ๋ ์์ํฌ์์ CorsUtils์ ์ํด ํ๋ฆฌํ๋ผ์ดํธ ์์ฒญ์ ๊ตฌ๋ถํ๋ ์กฐ๊ฑด์ ์๋์ ๊ฐ์ด ๊ตฌํ๋์ด์๋ค.
Javapublic abstract class CorsUtils { public static boolean isPreFlightRequest(HttpServletRequest request) { return (HttpMethod.OPTIONS.matches(request.getMethod()) && request.getHeader(HttpHeaders.ORIGIN) != null && request.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD) != null); } }
์คํ๋ง CORS ๋๋ฒ๊ทธ
CORS ์์ฒญ์ ๋ํ ๊ฒ์ฆ์ CorsFilter์์ ๊ธฐ๋ณธ์ ์ผ๋ก ์ฌ์ฉ๋๋๋ก ๊ตฌํ๋ DefaultCorsProcessor์ ์ํด ์ํ๋๋ค. CORS ์์ฒญ์ ์ํด ์๋ฐฐ๋๋ ์ํฉ์ ๋ํด์ ์์ธ์ ๋ก๊ทธ๋ก ์ถ๋ ฅํด๋ณด๋ ค๋ ๊ฒฝ์ฐ DefaultCorsProcessor์ ๋ํ ๋ก๊ทธ ๋ ๋ฒจ์ TRACE ๋๋ DEBUG๋ก ์ง์ ํ๋ฉด ๋๋ค. ๊ฐ๋ฐ ํ๊ฒฝ์์๋ CorsFilter ๋๋ DefaultCorsProcessor์ ์ฝ๋ ๋ผ์ธ์ ์ค๋จ์ ์ ๊ฑธ์ด์ ํ์ธํ ์ ์๊ฒ ์ง๋ง ์ด์ ํ๊ฒฝ์์๋ ๋ก๊ทธ ๋ ๋ฒจ๋ก ์ฒดํฌํ ์ ์์ ๊ฒ์ด๋ค.
application.ymllogging.level: org.springframework.web.cors.DefaultCorsProcessor: TRACE
์คํ๋ง MockMvc๋ก CORS ํ ์คํธ
์คํ๋ง ํ๋ ์์ํฌ๋ฅผ ์ฌ์ฉ์ค์ด๋ฉฐ CorsConfiguration ์ค์ ์ ํด๋์๋ค๋ฉด ์๋์ ๊ฐ์ด MockMvc๋ฅผ ํ์ฉํ ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํ ์ ์๋ค.
@Test
@DisplayName("Preflight request")
void TestPreflightRequest() {
Assertions.assertDoesNotThrow(() -> {
List<String> allowedOrigins = corsConfiguration.getAllowedOrigins();
if (allowedOrigins == null) {
allowedOrigins = new ArrayList<>();
}
List<String> allowedMethods = corsConfiguration.getAllowedMethods();
if (allowedMethods == null) {
allowedMethods = new ArrayList<>();
}
mockMvc.perform(options("/")
.header("Origin", allowedOrigins)
.header("Access-Control-Request-Method", "GET")
)
.andExpect(status().isOk())
.andExpect(header().stringValues("Access-Control-Allow-Origin", allowedOrigins.toArray(new String[]{})))
.andExpect(header().string("Access-Control-Allow-Methods", String.join(",", allowedMethods)))
.andDo(print());
});
}
cURL๋ก ํ ์คํธํ๋ ๋ฐฉ๋ฒ
ํฌ์คํธ๋งจ ๋๊ตฌ๋ก HTTP ์์ฒญ์ ์ํํ ๊ฒ์ฒ๋ผ cURL๋ก๋ ํ๋ฆฌํ๋ผ์ดํธ ์์ฒญ์ ์ํํด๋ณผ ์ ์๋ค. ํฌ์คํธ๋งจ๊ณผ๋ ๋ค๋ฅด๊ฒ OPTIONS๋ฅผ ์ง์ ์ ์ผ๋ก ์ฌ์ฉํด์ผํ๋ค. DefaultCorsProcessor์ ์ํด CORS์ ์๋ฐฐ๋ ์ํฉ์ด ์๋ค๋ฉด Invalid CORS request
์ด๋ผ๋ ์๋ต๊ณผ ํจ๊ป 403 ์ํ ์ฝ๋๊ฐ ํ์ธ ๋ ๊ฒ์ด๋ค.
Windows TerminalPS C:\Users\Mambo> curl -X OPTIONS 'http://localhost:5000' -H 'Origin: http://localhost' -H 'Access-Control-Request-Method: GET' -v * Trying 127.0.0.1:5000... * Connected to localhost (127.0.0.1) port 5000 (#0) > OPTIONS / HTTP/1.1 > Host: localhost:5000 > User-Agent: curl/8.0.1 > Accept: */* > Origin: http://localhost > Access-Control-Request-Method: GET > < HTTP/1.1 403 Forbidden < Expires: 0 < Cache-Control: no-cache, no-store, max-age=0, must-revalidate < X-XSS-Protection: 0 < Pragma: no-cache < X-Frame-Options: DENY < Date: Sat, 22 Jul 2023 12:56:00 GMT < Connection: keep-alive < Vary: Origin < Vary: Access-Control-Request-Method < Vary: Access-Control-Request-Headers < X-Content-Type-Options: nosniff < Transfer-Encoding: chunked < Invalid CORS request* Connection #0 to host localhost left intact
Postman์ผ๋ก ํ ์คํธํ๋ ๋ฐฉ๋ฒ
HTTP ์์ฒญ์ ์ํํด๋ณผ ์ ์๋ ํฌ์คํธ๋งจ์์ ํ๋ฆฌํ๋ผ์ดํธ ์์ฒญ์ ์ํํ๊ธฐ ์ํด์๋ Origin ํค๋๋ฅผ ํฌํจํ๋ฉด ๋๋ค. ํฌ์คํธ๋งจ์์ ์์์ OPTIONS๋ฅผ ์ํํ๋ฏ๋ก ๊ฐ๋จํ๊ฒ ํ ์คํธํด๋ณผ ์ ์๋ค.
๋ง์ฝ, CORS๋ ํ๋ก ํธ์๋ ์์ญ์์ ํด๊ฒฐํด์ผํ ๋ฌธ์ ๋ผ๊ณ ์๊ฐํ๊ณ ์๋ ๊ฐ๋ฐ์๊ฐ ์๋ค๋ฉด CORS์ ๋ํด์ ๋ค์ ํ์ตํ๊ธธ ๋ฐ๋๋ค.
ํ๋ก ํธ์๋ ๊ฐ๋ฐ์๊ฐ CORS์ ๊ณ ํต์์ ๋ฒ์ด๋ ์ ์๋๋ก ๋ฐฑ์๋ ๊ฐ๋ฐ์๋ ์ฑ
์์ ๋คํด์ผ ํ ๊ฒ์ด๋ค.