본문 바로가기
Computer Science/네트워크

CORS 에러 해결하기

by eunnnn 2023. 3. 9.

1. CORS를 이해하기 위한 기본 개념 - Origin

Origin은 URL에서 프로토콜, 도메인, 포트 번호를 합친 부분을 의미한다.

 

예를 들어, 다음과 같은 URL이 있다고 해보자.

URL : https://it-eldorado.com:80/posts/123456?data=789#abc

여기서 프로토콜(Scheme이라고도 함)에 해당하는 부분은 https://이고, 도메인에 해당하는 부분은 it-eldorado.com이며, 포트 번호에 해당하는 부분은 :80이다. 따라서 Origin은 https://it-eldorado.com:80이다.

 

2. CORS를 이해하기 위한 기본 개념 - SOP (Same Origin Policy)

SOP는 다른 Origin으로 요청을 보낼 수 없도록 금지하는 브라우저의 기본적인 보안 정책이다.

만약 SOP가 없다면 공격자는 사이트에 악성 스크립트를 심어 놓을 수 있다. 예를 들면, 다음과 같은 XSS 공격이 가능해진다.

<script>
fetch('http://google.com').then(/**/) // 이후 공격자에게 전송  
</script>

이 공격을 통해 공격자는 피해자가 'google.com'에 접속한 화면을 볼 수 있을 뿐만 아니라,
(가능하다면) 쿠키 값을 가져와 세션을 탈취할 수도 있을 것이다.

 

3. CORS란?

CORS(Cross Origin Resource Sharing)다른 Origin으로 요청을 보내기 위해 지켜야 하는 정책으로, 원래대로라면 SOP에 의해 막히게 될 요청을 풀어주는 정책이다.

동작 과정은 다음과 같다.

 

1. 기본적으로 웹 클라이언트 어플리케이션이 다른 출처의 리소스를 요청할 때는 HTTP 프로토콜을 사용하여 요청을 보내게 되는데, 이때 브라우저는 요청 헤더의 Origin 필드에 요청을 보내는 출처를 함께 담아보낸다.

2. 이후 서버가 이 요청에 대한 응답을 할 때 응답 헤더의 Access-Control-Allow-Origin이라는 값에 “이 리소스를 접근하는 것이 허용된 출처”를 내려준다.

3. 이후 응답을 받은 브라우저는 자신이 보냈던 요청의 Origin과 서버가 보내준 응답의 Access-Control-Allow-Origin을 비교해본 후 이 응답이 유효한 응답인지 아닌지를 결정한다.

만약 유효하지 않다면 그 응답을 사용하지 않고 버린다. 이러한 상황이 CORS 에러다.

위의 경우에는 유효하므로, 리소스를 문제없이 가져오게 된다.

 

 

CORS의 시나리오로는 1) Preflight / 2) Simple / 3) Credentialed Request 가 있다.

 

  • 1) Preflight request

 

브라우저가 본 요청을 보내기 전에 Preflight라고 부르는 예비 요청을 보내고, 이 Request에는 OPTION 메소드가 사용된다.

서버는 이 예비 요청에 대한 Response로 허용하는/금지하는 정보를 응답 헤더에 담아 보내준다.

친구의 집에 놀러가야하는 상황에서, 놀러가기 전에 놀러가도 되는지 확인하지 않는다면 큰 민폐일 수 있다. 때문에 친구에게 물어보고 가야할텐데 이 같은 작업이 Preflight Request라고 생각하면 쉽다.

이후 브라우저는 Request와 Response의 허용 정책을 비교한 후에, 안전하다고 판단되면 같은 end-point로 다시 본 요청을 보내게 된다.

 

이 과정을 통해 브라우저 스스로 이 요청을 보내는 것이 안전한지를 확인한다.

 

  • 2) Simple Request

 

이는 예비 요청을 생략하는 방법으로 간단하지만, 충족해야하는 조건들이 있다.

1. 요청의 메소드는 GET, HEAD, POST 중 하나여야 한다.
2. Accept, Accept-Language, Content-Language, Content-Type, DPR, Downlink, Save-Data, Viewport-Width, Width를 제외한 헤더를 사용하면 안된다.
3. 만약 Content-Type를 사용하는 경우에는 application/x-www-form-urlencoded, multipart/form-data, text/plain만 허용된다.

 

1번 같은 경우에는, 에러가 포함된 요청이 서버까지 가게 되면 PUT이나 DELETE  DB 상에 상이한 로직이 발생할 수 있기 때문이다.

그 이외에 조건은 만족시키기에 까다로워, 경험하기 쉽지는 않다.

 

4. CORS 에러 해결 방법

Controller에서 @CrossOrigin 어노테이션 추가하기

컨트롤러에서 특정 메서드 혹은 컨트롤러 상단부에 @CrossOrigin 만 추가하면 된다. 그러나 해당 방법의 경우 CORS 정책을 설정해야 할 대상이 많아지면 중복된 코드가 늘어나는 단점이 존재한다.

@RestController
@RequestMapping(value = "/api/threats", produces = "application/json")
@CrossOrigin(origins = "http://front-server.com") // 컨트롤러에서 설정
public class ThreatController {

    private final ThreatService threatService;

    public ThreatController(ThreatService threatService) {
        this.threatService = threatService;
    }

    @GetMapping
    @CrossOrigin(origins = "http://front-server.com") // 메서드에서 설정
    public ResponseEntity<ThreatLogCountResponse> getAllThreatLogs() {
        return ResponseEntity.ok(threatService.getAllThreatLogCount());
    }
}

 

WebMvcConfigurer를 이용해서 처리하기

앞서 말한 단점 때문에, 글로벌하게 config 파일에서 CORS 정책을 설정하는 것이 좋다.

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("http://localhost:8080")
                .allowedMethods(HttpMethod.GET.name());
    }
}

WebMvcConfigurer를 상속한 클래스를 만들고, addCorsMappings() 를 재정의하면된다. addMapping() 메소드를 통해 CORS 정책을 적용할 URL 패턴을 설정하고, allowedOrigins() 메소드를 통해 허용할 출처를 적어준다. 이 외에 allowedMethods() 메소드를 통해 GET, POST와 같은 HTTP 메소드의 종류도 제한할 수 있다.

 

CorsFilter 생성하기

Access-Control 을 확인할 수 있도록 커스텀 Filter 를 생성하는 방식이다.

@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:3000");
        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() {

    }
}

* Request headers (클라이언트의 요청 헤더)
    - Origin: 요청을 보내는 페이지의 도메인
    - Access-Control-Request-Method: 실제 요청하려는 메소드 종류
    - Access-Control-Request-Headers: 실제 요청에 포함되어 있는 헤더 이름

* Response headers (서버에서의 응답 헤더)
    - Access-Control-Allow-Origin: 요청을 허용하는 출처. * 인 경우, 모든 도메인의 요청을 허용한다.
    - Access-Control-Allow-Methods: 요청을 허용하는 메소드 종류. 헤더 값에 해당하는 메소드만 접근 허용한다. (default : GET, POST)
    - Access-Control-Allow-Headers: 요청을 허용하는 헤더 이름
    - Access-Control-Max-Age: 클라이언트에서 preflight 의 요청 결과를 저장할 시간(sec). 해당 시간동안은 preflight요청을 다시 하지 않게 된다.

 

 

프록시 서버 호스팅 & 구축

프록시 서버는 클라이언트가 프록시 서버 자신을 통해서 다른 네트워크 서비스에 간접적으로 접속할 수 있게 해준다.
쉽게 말해 브라우저와 서버 간의 통신을 도와주는 중계서버이다.

https://cors-anywhere.herokuapp.com

대표적으로 heroku Proxy 서버가 있다.
이 서버를 사용하면 중간에 요청을 가로채서 Access-Control-Allow-Origin : * 를 설정해서 응답해준다.

axios({
    method: "GET"
    url: `https://cors-anywhere.herokuapp.com/https://api.dropper.tech/covid19/status/korea?
    locale=${city}`,
    headers: {
    'APIKey': COVID_APIKEY,
},
})

요청해야 하는 URL앞에 프록시 서버 URL을 붙여서 요청하게 되면,
클라이언트에서 서버로 리소스를 요청할 때 발생하는 Cors 문제를 아주 간단하게 해결할 수 있다.

 

 

출처

더보기

https://evan-moon.github.io/2020/05/21/about-cors/

 

CORS는 왜 이렇게 우리를 힘들게 하는걸까?

이번 포스팅에서는 웹 개발자라면 한번쯤은 얻어맞아 봤을 법한 정책에 대한 이야기를 해보려고 한다. 사실 웹 개발을 하다보면 CORS 정책 위반으로 인해 에러가 발생하는 상황은 굉장히 흔해서

evan-moon.github.io

https://velog.io/@hunjison/SOP-CORS%EB%8A%94-%EB%B3%B4%EC%95%88%EC%97%90%EC%84%9C-%EC%99%9C-%ED%95%84%EC%9A%94%ED%95%A0%EA%B9%8C

 

SOP, CORS는 보안에서 왜 필요할까?

SOP, CORS를 공부하면서 어떤 개념인지도 대충 알 것 같은데,"그래서 이게 왜 필요해? 보안적으로 무슨 의미가 있어??"라는 의문이 들었다.이 포스팅에서는 그 내용에 대해 정리한다.기본 개념은 아

velog.io

https://inpa.tistory.com/entry/WEB-%F0%9F%93%9A-CORS-%F0%9F%92%AF-%EC%A0%95%EB%A6%AC-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95-%F0%9F%91%8F#%F0%9F%94%8D_%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80%EC%9D%98_cors_%EA%B8%B0%EB%B3%B8_%EB%8F%99%EC%9E%91_%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0

 

[WEB] 📚 악명 높은 CORS 개념 & 해결법 - 정리 끝판왕 👏

악명 높은 CORS 에러 메세지 웹 개발을 하다보면 반드시 마주치는 멍멍 같은 에러가 바로 CORS 이다. 웹 개발의 신입 신고식이라고 할 정도로, CORS는 누구나 한 번 정도는 겪게 된다고 해도 과언이

inpa.tistory.com

https://steady-coding.tistory.com/616

 

[Spring] Spring에서 CORS 이슈를 해결하는 방법

spring-study에서 스터디를 진행하고 있습니다. CORS(Cross-Origin Resource Sharing) CORS는 Cross-Origin Resource Sharing 의 줄임말로, 교차 출처 리소스 공유를 의미하며, 교차 출차는 ‘다른 출처’라고 생각하면

steady-coding.tistory.com

https://wonit.tistory.com/572

 

[Spring Boot] CORS 를 해결하는 3가지 방법 (Filter, @CrossOrigin, WebMvcConfigurer)

Server Side Template 방식이 아닌 Front와 Back 으로 나눠서 인프라를 구성해본 경험이 있는 사람들에게는 Cors가 매우 친숙할 수 있다. 현재 개발 흐름에서 웹 프로젝트를 진행하다가 Cors 를 만날 확률은

wonit.tistory.com

https://snupi.tistory.com/210

 

[Network] CORS 와 Proxy 우회

[ 2023/03 ] 2022/06에 작성한 내용을 보충하여 재발행합니다. ✈ CORS 아마추어 개발자들끼리 프로젝트를 진행하면 늘 한 번씩 CORS Error 를 보곤한다 원활한 문제 해결을 위해 이에 대한 개념을 더 살

snupi.tistory.com

https://devlog.kro.kr/postitem/?name=Cors#proxy

 

'Computer Science > 네트워크' 카테고리의 다른 글

웹 통신의 흐름  (0) 2023.03.13
TCP와 UDP  (1) 2023.03.12
네트워크 계층 구조  (0) 2023.03.12
REST의 정의와 HTTP 메소드  (0) 2023.03.10
HTTP와 HTTPS  (0) 2023.03.09