์ ๋ง ์ค๋๋ง์ ๊ฐ์ธ ํ๋ก์ ํธ๋ฅผ ์ผฐ๋๋ฐ ํ๋ฉด๋จ๊ณผ ์๋ฒ๊ฐ ์ฐ๊ฒฐ๋์ง ์์๋ค.
๋ด ํ๋ฆฐ ๊ธฐ์ต ์ ๋ถ๋ช ์ ์์ ์ผ๋ก ๋์๋๋ฐ (...)
Access to XMLHttpRequest at 'http://localhost:8081/api/diary/selectAll'
from origin 'http://localhost:8080' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
์ด์ฌํ ๊ตฌ๊ธ๋ง์ ํด๋ณด๋ SOP ์ ์ฑ ์ ์๋ฐํ์ฌ ๊ทธ๋ ๋ค๊ณ ํ๋ค.
SOP ์ ์ฑ ์ด๋ ๋ฌด์์ผ๊น ใ ก.ใ ก
โ SOP (Same-origin policy)
SOP๋ '๋์ผ ์ถ์ฒ ์ ์ฑ '์ ์ฝ์๋ก, ๋ณด์ ๋ฉ์ปค๋์ฆ์ด๋ค.
๋์ผ ์ถ์ฒ(origin)๋, ๋ ๊ฐ์ URL์ด ์์ ๋, ํ๋กํ ์ฝ + ํธ์คํธ + ํฌํธ๊ฐ ๋์ผํ ๊ฒฝ์ฐ๋ฅผ ๋งํ๋ค
์ฆ, ๋์ผ ์ถ์ฒ์ธ ๋ ์๋ค๋ผ๋ฆฌ๋ง ์ฐ๊ฒฐ์ ์ํจ๋ค๋ ์ ์ฑ ์ด๋ฏ๋ก API๋ฅผ ํธ์ถ ์, ์ด๋ฅผ ์๋ฐํ๋ฉด ๋ฐ์ดํฐ ์ ์ก์ ๋ง๋๋ค.
ํด๋น ์ ์ฑ ์ (๋ฐ์ดํฐ ์ก์์ ์, ํด์ปค๊ฐ ๊ฐ์ ํ ์ ์๋ ์ฌ์ง๊ฐ ์์ผ๋ฏ๋ก) ๋ณด์์์ ์ด์ ๋ก ๋ง๋ค์ด์ก๋ค.
โ ๏ธ
1. https://www.domain.com:5000 ์์ 'https://'๊ฐ ํ๋กํ ์ฝ, 'www.domain.com'์ด ํธ์คํธ, '5000'์ด ํฌํธ์ด๋ค.
2. ๋ง๋ ๊ฒ์ ์๋ฒ๊ฐ ์๋ ๋ธ๋ผ์ฐ์ ๊ณ , same-policy๋ ๋ธ๋ผ์ฐ์ ์ ๋ณด์ ์ ์ฑ ์ด๋ค.
3 ๋ช ๊ฐ์ง ์ํฉ์์๋ SOP์ ์ ์ฉํ์ง ์๋๋ค.
HTML ๋ด๋ถ ํธ์ถ์ SOP ์ ์ฑ ์ ๋ฌด์ํ๋ค. XHR (js์์์ ์์ฒญ)๋ง SOP์ด ๊ฑธ๋ฆฐ๋ค.
<script src...> , <link...> ๋ฑ์ ํ๊ทธ์์ ํ ์๋ฒ ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์๋ ์๋ฌด๋ฐ ๋ฌธ์ ๊ฐ ์์๋ ๊ฒ์ด ์ด ๋๋ฌธ์ด๋ค.
์ฅ ๊ทผ๋ฐ, API ์๋ฒ๋ ๋น์ฐํ ๋์ผ ํธ์คํธ๊ฐ ์๋ ํ๋ฅ ์ด ๋์๋ฐ ์ด์ฉ๋ ๋ง์ด๋
์ด๋ด ๋ ์ฐ๋ ๊ฒ์ด ๋ฐ๋ก CORS ํ๋ฌ๊ทธ์ธ์ด๋ค.
๊ทธ๋ผ CORS๋ ๋ฌด์์ผ๊น
โ CORS (Cross-Origin Resource Sharing)
CORS๋ '๊ต์ฐจ ์ถ์ฒ ์์ ๊ณต์ '์ ์ฝ์์ด๋ค. ๋์ผ ์ถ์ฒ๊ฐ ์๋ Cross Origin์์ ์ค๋ ๋ฐ์ดํฐ๋ ํ์ฉํด ์ค๋ค๋ ์๋ฏธ์ด๋ค.
CORS์ ๊ธฐ๋ณธ์ ์๋ฆฌ๋ ์ด๋ ๋ค.
โ
ํด๋ผ์ด์ธํธ๊ฐ ๋ค๋ฅธ origin์ผ๋ก ์์ฒญ์ ๋ณด๋ โถ ๋ณด๋ผ ๋ HTTP ์์ฒญ 'Origin' ํค๋์ ์์ ์ origin ์ค์ โถ ์๋ฒ๋ก๋ถํฐ ์๋ต๋ฐ์ ๋, 'Access-Control-Allow-Origin' ํค๋์ ์์ ์ origin์ด ์๋์ง ํ์ธ
* ์ด๋, Origin ํค๋์ ์์ผ๋์นด๋(*)๋ฅผ ์ฐ๋ฉด ๋ชจ๋ origin์ ํ์ฉํ๋ค๋ ์๋ฏธ๊ฐ ๋๋ค.
์? ๊ทผ๋ฐ HTTP ์์ฒญ์ ๋ณด๋ผ ๋, Origin์ด๋ผ๋ ํค๋๊ฐ ์์๋๊ฐ
์ฌ์ค ๋ธ๋ผ์ฐ์ ๋ ์์ฒญ์ ๋ณด๋ด๊ธฐ ์ , '์๋น ์์ฒญ'์ด๋ผ๋ ๊ฒ์ ๋ณด๋ธ ๋ค '๋ณธ ์์ฒญ'์ ๋ณด๋ธ๋ค. (100%๋ ์๋๊ณ , ๋๋ถ๋ถ)
๊ทธ๊ฒ์ Preflight๋ผ๊ณ ๋ถ๋ฅด๋ฉฐ, ์ด ๋ HTTP ๋ฉ์๋๊ฐ GET/POST๊ฐ ์๋ OPTION์ด๋ผ๋ ์์ฒญ์ ์ฌ์ฉํ๋ค.
๋ณธ ์์ฒญ์ xhr ๋ผ๊ณ ์นญํ๊ณ , ์๋น ์์ฒญ์ preflight๋ผ๊ณ ์นญํ๋ค.
โ ๏ธ
1. ์ธ์ '๋จ์ ์์ฒญ'์ด๋ผ๋ ๊ฒ๋ ์๋๋ฐ '์๋น ์์ฒญ'์ ์๋ตํ๊ณ ๋ฐ๋ก '๋ณธ ์์ฒญ'์ ๋ณด๋ด๋ ๊ฒ์ด๋ค.
๋ช ๊ฐ์ง ์กฐ๊ฑด์ ๋ง์กฑํด์ผ๋ง ํ๋ค.
2. '์ธ์ฆ๋ ์์ฒญ'์ด๋ผ๋ ๊ฒ๋ ์๋ค.
์ด๋ ์๊ฒฉ ์ธ์ฆ ์ ๋ณด๋ฅผ ์ค์ด ์์ฒญํ๋ ๊ฒฝ์ฐ๋ก, ์ฟ ํค๋ ํ ํฐ ๊ฐ์ ๋ณด๋ผ ๋๋ฅผ ์๋ฏธํ๋ค. ์ฝ๊ฐ ๋ฐฉ์์ด ๋ค๋ฅด๋ค.
* ๋๋ถ๋ถ์ API ์์ฒญ์ด Preflight์ด๋ฏ๋ก, ์ฌ๊ธฐ์๋ ์๋ต์๋ต
์ ๊ทธ๋์ ํด๊ฒฐํ๋ ค๋ฉด ์ด๋ป๊ฒ ํด์ผํ๋๋ฐ์
โ ํด๋ผ์ด์ธํธ๋จ์์ ํด๊ฒฐ
1. ๋ธ๋ผ์ฐ์ ํ๋ฌ๊ทธ์ธ ์ฌ์ฉ
ํฌ๋กฌ์ ๊ฒฝ์ฐ 'Allow CORS: Access-Control-Allow-Origin'์ด๋ผ ํ์ฌ ํด๋น ๋ฌธ์ ๋ฅผ ํด๊ฒฐํด ์ฃผ๋ ํ์ฅ ํ๋ก๊ทธ๋จ์ด ์๋ค.
๋ก์ปฌ ํ๊ฒฝ์์ ์ ๊น ํ ์คํธํ ๋ ํธํ ๋ฏ
2. http-proxy-middleware ์ฌ์ฉ
๋ง์ฐฌ๊ฐ์ง๋ก, ๋ก์ปฌ์ธ ๊ฒฝ์ฐ์ ํ์ ํ์ฌ ํด๋น ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํตํด ํด๋ผ์ด์ธํธ ๋จ์์ ํด๊ฒฐํ ์ ์๋ค.
2. ํ๋ก์ ์ฌ์ฉํ๊ธฐ
ํ๋ก์(proxy)๋ ํด๋ผ์ด์ธํธ์ ์๋ฒ ์ฌ์ด์ ๋๋ฆฌ์ ์ญํ ์ ํด์ฃผ๋ ์ค๊ณ ์๋ฒ์ด๋ค.
๋ชจ๋ ์ถ์ฒ๋ฅผ ํ์ฉํ๋ ํ๋ก์ ์๋ฒ๋ฅผ ๊ฐ์ด๋ฐ์ ๋ผ์ฐ๋ฉด ๋๋ค. SOP์ ์ฐํํ๋ ๋ฐฉ๋ฒ.
ํ๋ฉด ์์ผ๋ก๋, ํด๋ผ์ด์ธํธ์ IP๋ก ์์ฒญ์ด ๋ ์๊ฐ๋ ๊ฒ์ฒ๋ผ ๋ณด์ด์ง๋ง ํ๋ก์๋ฅผ ๊ฑฐ์น๋ค.
๋ค๋ง, ๋ฌด๋ฃ ํ๋ก์ ์๋ฒ๋ API ์์ฒญ ํ์์ ์ ํ์ด ์์ผ๋ฏ๋ก ์ด๊ฒ ๋ํ ํ ์คํธ ์ฉ๋๋ก ๋ช ๋ฒ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข๋ค.
์๋๋ฉด ์ง์ ํ๋ก์ ์๋ฒ๋ฅผ ๊ตฌํํด์ผ ํ๋ค.
์๋ ์์๋ ํ๋ก์ค ์๋ฒ ์ค ํ๋์ธ cors proxy app ํ๋ก์ ์๋ฒ์ด๋ค.
<script src='https://cdnjs.cloudflare.com/ajax/libs/axios/1.1.3/axios.min.js'></script>
<script>
axios({
url: 'https://cors-proxy.org/api/',
method: 'get',
headers: {
'cors-proxy-url' : 'https://localhost:8000/' //์ฌ์ฉํ URL๋ก ๋ณ๊ฒฝ
},
}).then((res) => {
console.log(res.data);
})
</script>
โ ์๋ฒ๋จ์์ ํด๊ฒฐ
์ ์์ ์ธ ํด๊ฒฐ ๋ฐฉ์์ ์๋ฒ์์ ์ ํ ํ๋ ๊ฒ์ด๋ค. ๋ฐฉ๋ฒ์ ์ด 3๊ฐ์ง์ด๋ค.
1. Controller์ @CrossOrigin ์ด๋ ธํ ์ด์ ์ฌ์ฉํ๊ธฐ
@RequestMapping("api/test")
@RestController
@RequiredArgsConstructor
@CrossOrigin
public class TestController {
...
}
2. WebConfig ํ์ผ ์์ฑ
(1) ์ฒซ๋ฒ์งธ ๋ฐฉ๋ฒ
WebMvcConfigurer์ ์์๋ฐ๋ WebConfig ํด๋์ค๋ฅผ ๋ง๋ค์ด ๋ค์๊ณผ ๊ฐ์ด ์ ํ ํด์ค๋ค.
ํด๋์ค๋ฅผ ์ด๋ ๊ฒ ๋ฐ๋ก ๋ง๋ค๊ฑฐ๋ฉด, @Configuration ์ด๋ ธํ ์ด์ ์ ๋ถ์ฌ์ค๋ค.
allowedOrigins์ ์๋ต์ ์ ํด์ค origin์ ์ ์ผ๋ฉด ๋๋ค.
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:8080/");
}
}
(2) ๋๋ฒ์งธ ๋ฐฉ๋ฒ
@SpringBootApplication ์ด๋ ธํ ์ด์ ์ด ๋ถ์ main ํ์ผ์ @Bean ์ผ๋ก ๋ฑ๋กํด์ฃผ์ด๋ ๋๋ค.
package com.eaproj.one;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@SpringBootApplication
public class Application extends SpringBootServletInitializer {
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("http://localhost:8080/");
}
};
}
}
3. CorsFilter๋ฅผ ๋ง๋ค์ด Access-Control-Allow-Origin ํค๋ ์ธํ
Filter๋ฅผ ๋ง๋ค์ด์ค๋ค.
response.setHeader("Access-Control-Allow-Origin", "http://localhost:8080");
์ด ๋ถ๋ถ์์ origin์ * ๋ก ๋ฐ๊พธ๋ฉด
ํน์ origin์ด ์๋, ๋ชจ๋ origin์๊ฒ ์๋ตํ ์ ์๋๋ฐ ๋น์ฐํ ๋ณด์์ ์ทจ์ฝํ ์ ๋ฐ์ ์๋ค.
package com.eaproj.one.config;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CorsFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "http://localhost:8080");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods","*");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept, Authorization");
if("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
}else {
chain.doFilter(req, res);
}
}
@Override
public void destroy() {
}
}
โถ ๊ธ์ ์์ฑํ๋ ๋์ค, CORS์ ๋ฌธ์ ๊ฐ ์๋๋ผ๋ ๊ฒ์ ์์๋ค. ํด๋น ์ปจํธ๋กค๋ฌ์๋ @CorssOrigin์ด ์ ๋ถ์ด์์๋๋ฐ, ์ปจํธ๋กค๋ฌ ์์ฒด๋ฅผ ์ฐพ์ง ๋ชปํ๋ SOP ์ ์ฑ ์๋ฐ์ด๋ผ๋ ์๋ฌ๊ฐ ๋ฌ ๊ฒ
ํจํค์ง๋ฅผ ์ฎ๊ธฐ๋ฉด์ ํ์ผ์ ์ ๋ฆฌํ ์ ์ด ์์๋๋ฐ ๊ทธ๋ ์ปจํธ๋กค๋ฌ ๊ด๋ จ ์ด๋ ธํ ์ด์ ์ด ๋น ์ง ๋ฏ ํจ
๊ทธ๋ ์๋ ๋์๋ค๋๊น .. TT ๊ทธ๋๋ ๋ ์์ธํ๊ฒ ๊ณต๋ถํ๊ฒ ๋์ด์ ์ข์๋ค.
๋ Controller ๋ง๋ค ์ด๋ ธํ ์ด์ ์ ๋ถ์ด๋ ๊ฒ์ ๋นํจ์จ์ ์ธ ๊ฒ ๊ฐ์์ CorsFilter๋ฅผ ๋ง๋ค์๋ค.
3๊ฐ์ง ๋ฐฉ๋ฒ ๋ค ํ ์คํธ ํด๋ณด์๊ณ ๋ชจ๋ ์ ์ ๋์ํ๋ค.
๋-!!