CORS 에러는 브라우저가 외부 서버로의 Ajax 요청을 차단할 때 발생하는 문제로, SOP(Same-Origin Policy) 가 그 원인으로, 출처가 다를 경우 요청을 차단하는 보안 정책이다.
필자는 RSS, API, Web 크롤링을 테스트하면서 이 문제를 만났고, 다양한 방법을 시도했다.
Spring 프로젝트에서는 web.xml에 필터를 추가하여 CORS 허용을 시도할 수 있으며, 다음과 같이 설정했다:
<filter>
<filter-name>CorsFilter</filter-name>
<filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
<init-param>
<param-name>cors.allowed.origins</param-name>
<param-value>*</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CorsFilter</filter-name>
<url-pattern>*</url-pattern>
</filter-mapping>
Tomcat CORS 필터 설정을 해 보았지만, 외부 API 요청의 CORS 문제는 해결되지 않았다.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:8080")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*")
.allowCredentials(true);
}
}
<http>
<cors />
<!-- 기타 보안 설정 -->
</http>
Spring Security의 CORS 필터를 사용해 시도했지만, 외부 서버와의 통신에서 여전히 CORS 문제가 발생했다.
public class CORSFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Content-Type, x-requested-with, Authorization");
chain.doFilter(req, res);
}
}
<filter>
<filter-name>CORSFilter</filter-name>
<filter-class>com.example.CORSFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CORSFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
이 방법으로도 외부 서버와의 CORS 문제를 해결할 수 없었다.
최종적으로, 프록시 서버를 사용하여 CORS 문제를 해결했다.
프록시 서버가 클라이언트의 요청을 받아 외부 서버로 요청을 보내는 방식
@Controller
public class ProxyController {
@GetMapping("/proxy/crawl")
public ResponseEntity<String> proxyCrawlRequest(@RequestParam String url) {
RestTemplate restTemplate = new RestTemplate();
try {
ResponseEntity<byte[]> response = restTemplate.getForEntity(url, byte[].class);
String body = new String(response.getBody(), StandardCharsets.UTF_8);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(new MediaType("application", "rss+xml", StandardCharsets.UTF_8));
return new ResponseEntity<>(body, headers, HttpStatus.OK);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.BAD_GATEWAY).body("데이터를 가져오지 못했습니다: " + e.getMessage());
}
}
}
async function parseRSSFeed(feedUrl) {
let response;
try {
response = await fetch(`http://localhost:8080/proxy/crawl?url=${encodeURIComponent(feedUrl)}`, {
headers: {
'User-Agent': 'Mozilla/5.0',
'Accept': 'application/rss+xml, application/xml',
'Accept-Charset': 'UTF-8'
}
});
if (!response.ok) {
console.error(`프록시를 통한 HTTP 에러: ${response.status}`);
return null;
}
} catch (error) {
console.error("프록시 요청 중 오류 발생:", error);
return null;
}
const text = await response.text();
const parser = new DOMParser();
const xml = parser.parseFromString(text, "application/xml");
console.log('RSS Feed:', xml);
return xml;
}
이 방식으로 프록시 서버를 통해 외부 서버로 요청을 보내 CORS 문제를 해결했다.
Spring Controller가 프록시 역할을 하여 클라이언트의 CORS 제한을 우회할 수 있었다.
CORS 문제는 출처가 다른 클라이언트와 외부 서버 간의 보안 정책으로 인해 발생하며, 여러 방법을 시도했지만, 서버 설정만으로는 해결되지 않았다.
프록시 서버를 통해 요청을 우회하는 방식이 가장 효과적이었으며, Spring과 JavaScript의 연동으로 이 문제를 해결할 수 있었다.
프록시 방식을 선택한 이유는, 동일한 출처의 localhost:8080에서 http://localhost:3000으로 요청할 때는 web.xml 또는 security-context.xml 설정으로 해결할 수 있겠지만 localhost:8080에서 https://www.naver.com** 처럼 외부 URL로 요청하는 경우, 해당 서버의 CORS 정책을 우회할 수 없으므로 프록시 서버를 통해 해결했다.