포스트

2021-05-11-TIL

2021-05-11-TIL

크로스 도메인(CORS) 이슈

원인

웹페이지의 origin과 API의 origin이 달라서 발생

origin은 스킴(http, https), 호스트, 포트의 조합이다. 셋 중 하나만 달라도 다른 origin이다. 그래서 http://localhost:3000에서 뜬 페이지가 http://localhost:8080의 API를 부르는 것도 교차 출처 요청이다(MDN CORS).

브라우저는 보안상 스크립트(fetch, XMLHttpRequest)가 보내는 교차 출처 요청에 동일 출처 정책을 적용한다. CORS는 서버가 응답 헤더로 “이 origin은 내 리소스를 읽어도 된다”고 알려 주는 방법이다. 서버가 Access-Control-Allow-Origin 헤더를 보내지 않으면 요청은 서버까지 갔더라도 브라우저가 스크립트에 응답을 넘겨주지 않는다. Postman이나 curl에서는 잘 되는데 브라우저에서만 실패하는 이유가 이것이다.


Preflight 요청

GET, HEAD, POST 중 하나이고 헤더와 Content-Type이 허용 목록 안에 있는 단순 요청(simple request)이 아니면, 브라우저는 실제 요청 전에 OPTIONS 메서드로 사전 요청(preflight)을 보낸다. JSON을 보내는 Content-Type: application/json 요청도 여기에 해당한다.

sequenceDiagram
  participant B as 브라우저 (3000 포트)
  participant S as API 서버 (8080 포트)
  B->>S: OPTIONS /api/items, Origin과 Access-Control-Request-Method
  S-->>B: 204, Access-Control-Allow-Origin과 Allow-Methods
  B->>S: POST /api/items (실제 요청)
  S-->>B: 200, Access-Control-Allow-Origin

해결책

  • CORS 정책을 지키도록 요청 헤더 변경
  • API에서 허용하도록 설정 변경 -> @CrossOrigin을 컨트롤러에 붙여주기

결국 허용 여부는 서버가 정한다. Spring MVC에서는 @CrossOrigin을 컨트롤러 클래스나 메서드에 붙인다. 아무 속성 없이 붙이면 모든 origin, 모든 헤더, 매핑된 모든 HTTP 메서드를 허용하고, 자격 증명(쿠키)은 허용하지 않는다(Spring 문서).

1
2
3
@CrossOrigin(origins = "http://localhost:3000")
@RestController
public class ItemController { ... }

컨트롤러가 많아지면 WebMvcConfigurer의 addCorsMappings로 전역 설정을 한곳에 두는 편이 관리하기 쉽다. 쿠키를 함께 보내야 하면 Access-Control-Allow-Origin에 *를 쓸 수 없고 origin을 명시해야 한다.

참고한 자료외부 출처 4

외부 출처

이 글은 저작권자의 CC BY 4.0 라이선스를 따릅니다.

댓글

아직 댓글이 없습니다