CORS๋?
CORS(Cross-Origin Resource Sharing)๋ ์ถ์ฒ๊ฐ ๋ค๋ฅธ ์์๋ค์ ๊ณต์ ํ๋ค๋ ๋ป์ผ๋ก, ํ ์ถ์ฒ์ ์๋ ์์์์ ๋ค๋ฅธ ์ถ์ฒ์ ์๋ ์์์ ์ ๊ทผํ๋๋ก ํ๋ ๊ฐ๋ ์ด๋ค. ์ง์ญํ๋ฉด, ๊ต์ฐจ๋๋ ์ถ์ฒ ์์๋ค์ ๊ณต์ ! ๋ค๋ฅธ ์ถ์ฒ์ ์๋ ์์์ ์์ฒญํ๋ค๊ณ ํ๋ฉด, ์ด๋ฅผ ๊ต์ฐจ ์ถ์ฒ ์์ฒญ์ด๋ผ๊ณ ๋ถ๋ฅธ๋ค.
์ถ์ฒ๋?
์์ ๊ตฌ์ฑ์์ ์ค์์ Protocol + Host + Port 3๊ฐ์ง๊ฐ ๊ฐ์ผ๋ฉด ๋์ผ ์ถ์ฒ(Origin)๋ผ๊ณ ํ๋ค.
๋์ผ ์ถ์ฒ ์ ์ฑ (Same-origin policy)
๋์ผ ์ถ์ฒ ์ ์ฑ
(Same-origin policy)๋ ๋ค๋ฅธ ์ถ์ฒ๋ก๋ถํฐ ์กฐํ ๋ ์์๋ค์ ์ฝ๊ธฐ ์ ๊ทผ์ ๋ง์ ๋ค๋ฅธ ์ถ์ฒ ๊ณต๊ฒฉ์ ์๋ฐฉํ๋ค. ๊ทธ๋ฌ๋, ๋ค๋ฅธ ์ถ์ฒ์์ ์ป์ ์ด๋ฏธ์ง๋ฅผ ๋ด๋ img, ์ธ๋ถ ์ฃผ์๋ฅผ ๋ด๋ link ๊ฐ์ ์ฌ๋ฌ ํ๊ทธ๋ค์ ํ์ฉํ๋ค. ๋์ผ ์ถ์ฒ ์ ์ฑ
์ ์ ํํ ๊ตฌํ ๋ช
์ธ๋ ์์ง๋ง ์ต์ ์ ๋ธ๋ผ์ฐ์ ๋ค์ ์ผ์ ๊ท์น์ ๋ฐ๋ฅด๊ณ ์๋ค.
์์ฒญํ๋ ํด๋ผ์ด์ธํธ์ ์์ฒญ๋ฐ๋ ์๋ฒ๊ฐ ๊ฐ์ ์ถ์ฒ์ ์์ผ๋ฉด ๋์ผ ์ถ์ฒ, ์๋ก ๋ค๋ฅธ ์๋ฒ์ ์์ผ๋ฉด ๋ค๋ฅธ ์ถ์ฒ ์์ฒญ
์์ ํ๊ทธ๋ค ์ ์ธํ๊ณ ์ด๊ฑธ ํ์ฉํ๋ ๊ฒ๋ค์ด ๋ช ๊ฐ ์๋ค.
1. ๋จ์์์ฒญ(Simple Request)
-
GET, HEAD, POST ์์ฒญ๋ง ๊ฐ๋ฅ๋ค
-
Accept, Accept-Language, Contet-Language, Content-Type๊ณผ ๊ฐ์ CORS ์์ ๋ฆฌ์คํธ ํค๋ ํน์ User-Agent ํค๋
-
Contet-Type ํค๋๋ application/x-www-form-urlencoded, multipart/form-data and text/plain๋ง ๊ฐ๋ฅ
-
XMLHttpRequest ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ์ฌ ์์ฒญํ๋ฉด, ์์ฒญ์์ ์ฌ์ฉ๋ XMLHttpRequest.upload์ ์ํด ๋ฐํ๋๋ ๊ฐ์ฒด์ ์ด๋ ํ ์ด๋ฒคํธ ๋ฆฌ์ค๋๋ ๋ฑ๋ก๋์ง ์๋๋ค.
์ฝ๋๋ก ํ์ธ.
const xhr = new XMLHttpRequest();
const url = 'https://www.api.com?q=test';
xhr.open('GET', url);
xhr.onreadystatechange = requestHandler;
xhr.send();
๋ง์ฝ ์๋ฒ๊ฐ ์ด ํค๋์ ์๋ตํ์ง ์๊ฑฐ๋, ํค๋ ๊ฐ์ด ์์ฒญ์ ์ถ์ฒ์ ์ผ์นํ์ง ์๋ ๋๋ฉ์ธ์ธ ๊ฒฝ์ฐ, ๋ธ๋ผ์ฐ์ ๋ ์๋ต์ ์ฐจ๋จํ๋ค. ๋ํ ์์ฒญํ ์ถ์ฒ๊ฐ ์๋ฒ์ access-conrol-allow-origin์ ํฌํจ๋์ด ์๋ ๊ฒฝ์ฐ๋ ๋ง์ฐฌ๊ฐ์ง์ด๋ค.
2. ํ๋ฆฌ ํ๋ผ์ดํธ(Preflight Request) ์ด๊ฒ ์ ์ผ ์ข์๋ฏ
ํ๋ฆฌ ํ๋ผ์ดํธ๋ OPTIONS ๋ฉ์๋๋ก HTTP ์์ฒญ์ ๋ฏธ๋ฆฌ ๋ณด๋ด ์ค์ ์์ฒญ์ด ์ ์กํ๊ธฐ์ ์์ ํ์ง ํ์ธํฉ๋๋ค. ๋ค๋ฅธ ์ถ์ฒ ์์ฒญ์ด ์ ์ ๋ฐ์ดํฐ์ ์ํฅ์ ์ค ์ ์๊ธฐ ๋๋ฌธ์ ๋ฏธ๋ฆฌ ์ ์กํ๋ค๋ ์๋ฏธ์ ๋๋ค.
์์ฒญ ํค๋์๋ ๋ค์ ๊ฐ์ด ํฌํจ๋ฉ๋๋ค.
origin : ์ด๋์ ์์ฒญ์ ํ๋์ง ์๋ฒ์ ์๋ ค์ฃผ๋ ์ฃผ์
access-control-request-method : ์ค์ ์์ฒญ์ด ๋ณด๋ผ HTTP ๋ฉ์๋
access-control-request-headers : ์ค์ ์์ฒญ์ ํฌํจ๋ header
์๋ต ํค๋์๋ ๋ค์ ๊ฐ์ด ํฌํจ๋ฉ๋๋ค.
access-control-allow-origin : ์๋ฒ๊ฐ ํ์ฉํ๋ ์ถ์ฒ
access-control-allow-methods : ์๋ฒ๊ฐ ํ์ฉํ๋ HTTP ๋ฉ์๋ ๋ฆฌ์คํธ
access-control-allow-headers : ์๋ฒ๊ฐ ํ์ฉํ๋ header ๋ฆฌ์คํธ
access-control-max-age : ํ๋ฆฌ ํ๋ผ์ดํธ ์์ฒญ์ ์๋ต์ ์บ์์ ์ ์ฅํ๋ ์๊ฐ
const xhr = new XMLHttpRequest();
const url = 'https://www.api.com?q=test';
xhr.open(โGET', url);
xhr.setRequestHeader(โcustom-header', โtest')
xhr.onreadystatechange = requestHandler;
xhr.send();
3. ์ ์ฉ ์์ฒญ(Credentialed Request)
์ ์ฉ ์์ฒญ์ ์ฟ ํค, ์ธ์ฆ ํค๋, TLS ํด๋ผ์ด์ธํธ ์ธ์ฆ์ ๋ฑ์ ์ ์ฉ์ ๋ณด์ ํจ๊ป ์์ฒญํฉ๋๋ค. ๊ธฐ๋ณธ์ ์ผ๋ก, CORS ์ ์ฑ ์ ๋ค๋ฅธ ์ถ์ฒ ์์ฒญ์ ์ธ์ฆ์ ๋ณด ํฌํจ์ ํ์ฉํ์ง ์์ต๋๋ค. ์์ฒญ์ ์ธ์ฆ์ ํฌํจํ๋ ํ๋๊ทธ๊ฐ ์๊ฑฐ๋ access-control-allow-credentials๊ฐ true๋ก ์ค์ ํ๋ค๋ฉด ์์ฒญํ ์ ์์ต๋๋ค.
์ธ์ฆ์ ๋ณด๋ฅผ ํฌํจํ์ฌ ์์ฒญ์ ๋ณด๋ด๊ฒ ์ต๋๋ค.
const xhr = new XMLHttpRequest();
const url = 'https://www.api.com?q=test';
xhr.open('GET', url);
xhr.withCredentials = true;
xhr.send();
๋ง์ฝ ์๋ฒ ์๋ต์ access-control-allow-credentials ๊ฐ true๋ก ์ค์ ๋์ง ์์๊ฑฐ๋ access-control-allow-origin ํค๋์ ์๋ ๊ฐ์ด ํ์ฉ๋ ์ถ์ฒ๊ฐ ์๋๋ผ๋ฉด ์๋์ ๊ฐ์ด ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค.
Spring Boot์์ cross-origin ์ค์ ํ๊ธฐ
spring Boot์์ cross-origin์ ์ค์ ํ๋ ๋ฐฉ๋ฒ์ ์์๋ณด๊ฒ ์ต๋๋ค. ๋ฉ์๋ ์ค์ , ์ปจํธ๋กค๋ฌ ์ค์ ์ด ์์ผ๋ฉฐ ๊ฐ๋ณ์ ์ผ๋ก ์ ์ฉ ํ ์๋ ์์ต๋๋ค.
๋ฉ์๋์ ์ค์ ํ๊ธฐ
@RestController
@RequestMapping("/account")
public class AccountController {
@CrossOrigin
@RequestMapping(method = RequestMethod.GET, path = "/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
@RequestMapping(method = RequestMethod.DELETE, path = "/{id}")
public void remove(@PathVariable Long id) {
// ...
}
}
retrieve() ๋ฉ์๋์ ์ ์ธ๋ @CrossOrigin์ ๊ธฐ๋ณธ ์ค์ ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
-
๋ชจ๋ ์ถ์ฒ๊ฐ ํ์ฉ๋ฉ๋๋ค.
-
ํ์ฉ๋ HTTP ๋ฉ์๋๋ @RequestMapping์ ์ ์ธ๋ ๋ฉ์๋๋ค์ ๋๋ค.
-
ํ๋ฆฌํ๋ผ์ดํธ ์๋ต์ 30๋ถ ๋์ ์บ์๋ฉ๋๋ค.
์ปจํธ๋กค๋ฌ์ ์ค์ ํ๊ธฐ
@CrossOrigin(origins = "http://example.com", maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {
@RequestMapping(method = RequestMethod.GET, path = "/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
@RequestMapping(method = RequestMethod.DELETE, path = "/{id}")
public void remove(@PathVariable Long id) {
// ...
}
}
์ปจํธ๋กค๋ฌ์ ์ค์ ํ์ผ๋ฏ๋ก AccountController์ ์๋ retrieve() ์ remove() ํจ์ ๋ชจ๋์ ์ ์ฉ๋ฉ๋๋ค.
๊ฐ๋ณ ์ ์ฉ์ํค๊ธฐ
@CrossOrigin(maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {
@CrossOrigin("http://example.com")
@RequestMapping(method = RequestMethod.GET, "/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
@RequestMapping(method = RequestMethod.DELETE, path = "/{id}")
public void remove(@PathVariable Long id) {
// ...
}
}
์คํ๋ง์์ ์ฌ๋ฌ๊ฐ์ง CORS ์ ์ฑ ์ ๋ณตํฉํด์ ์ค์ ํ ์ ์์ต๋๋ค.
๋ชจ๋ ๋ฉ์์๋๋ค์ 3600์ด๊ฐ ์บ์ ์๊ฐ์ ๋๋ค. retrieve() ๋ฉ์๋๋ ํ์ฉ ์ถ์ฒ๊ฐ โhttp://example.comโ ๋ฐ์ ์๋ฉ๋๋ค. ํ์ง๋ง, remove() ๋ฉ์๋๋ ๋ณ๋์ ์ค์ ์ด ์์ผ๋ฏ๋ก ๋ชจ๋ ์ถ์ฒ๊ฐ ๊ฐ๋ฅํฉ๋๋ค.
์ ์ญ ์ค์ ํ๊ธฐ
CORS ์ ์ฑ ์ ์ค์ ์ WebMvcConfigurer๋ฅผ ๊ตฌํํ์ฌ ์ค์ ํ ์ ์์ต๋๋ค. ์ด๋ ํํฐ ๊ธฐ๋ฐ์ด๊ธฐ ๋๋ฌธ์, ์ ์ญ์ ์ผ๋ก ๋ชจ๋ ์์ฒญ์ ๋ํด์ ๊ฒ์ฌํฉ๋๋ค.
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**");
}
}