프론트엔드

[프론트엔드] CORS 에러 해결

KyuminKim 2024. 1. 7. 19:56

 

상황

프로젝트 진행 중 웹사이트 구축하여, 백엔드와 프론트엔드를 연결하려 할 때 발생했다.

 

- 프론트엔드: react로 구축 (localhost:3000)

- 백엔드: go gin으로 구축 (localhost:8081)

 

현재, 백엔드 API와 postman 테스트는 원활하게 잘 된다 !


문제

에러 고르에서 설명하는 이유는 CORS (Cross Origin Resource Sharing) 에러이다.

에러 화면
개발자 모드 확인

 

우선 이를 알아보기 전, 비슷한 개념인 Same Origin Policy부터 알아보자.


Same origin Policy (동일 출처 정책) 에러

 

Origin(출처)란 protocol, host(=domain), port 을 합친 것을 말한다.

즉, 이 셋 중에서 하나라도 다르다면 다른 Origin 이라는 것이다 

 

즉, 백엔드 서버의 origin과 프론트엔드(웹 브라우저)의 origin이 다르므로 , 

웹 브라우저 (프론트엔드)에서 발생하는 에러이다 !


CORS

두 출처가 완전히 다를 때 발생하는 에러가 동일 출처 정책이다.

 

그러면 CORS는 뭐지?

 

CORS Cross-Origin Resource Sharing 로, Same Origin Policy를 완화한 매커니즘이다.

 

즉, (웹 브라우저와는) 서로 다른 출처로부터의 리소스를 허용하나, 대신 헤더에 CORS 관련 내용을 넣어 인증을 하는 방식이다.

Same origin VS Cross origin (출처: https://developer.mozilla.org/ko/docs/Web/HTTP/CORS)

 

preflight request (예비 요청)

'헤더에 CORS 관련 내용을 넣어 인증' 한다고 했는데, 조금 더 자세히 살펴보자.

 

통신은 보편적으로 다음과 같이 이루어진다.

 

# 1. preflight 요청 

1. 프론트엔드 -> 백엔드:  [OPTION] method의 요청 전송 (preflight라고 하는, 예비 요청이다!)

2. 백엔드 처리

3. 백엔드 -> 프론트엔드: 1번 요청에 대한 응답

 

# 2. 실제 요청

4. 프론트엔드 -> 백엔드: [POST/GET/PUT, ...] method의 원하는 요청 전송    

   -  Origin 헤더 필드에 출처(프론트엔드)추가해 전송 

5. 백엔드 처리

6. 백엔드 -> 프론트엔드: 5번 요청에 대한 응답    

   - Access-Control-Allow-Origin 헤더 필드에 주소(4번 요청이 온 곳 = 프론트엔드) 추가해 전송

7. 프론트엔드: CORS정책에 위반되는지 확인 (4번 origin과 6번 origin 비교)

 

(* 4, 6번의 출처는 백엔드에서 전송하는 리소스(6번)에 접근 가능한 origin을 말한다.)

 

즉,  preflight라는 예비 요청을 통해 통신하기에 적절한 서버인지 확인하고 + 실제 요청을 수행하는 방식으로 통신이 수행된다.

 

실제로 백엔드 서버에서 

백엔드 로그

이와 같은 로그를 확인할 수 있었다.

OPTIONS 메소드가 가리키는 것이 preflight를,

POST 메소드가 가리키는 것이 원래 요청하고자 한 것을 의미한다.

 

흥미로운 것은, 백엔드에서는 post요청에 대한 응답을 잘 보낸다는 것이다. (200 ok)

즉, CORS 정책은 프론트엔드의 정책이라는 것을 다시 한 번 이해할 수 있다.

 


 

문제 발생 이유

다시 돌아와서 내가 에러가 발생한 이유를 살펴보자.

 

 

# 1. preflight 요청 

1. 프론트엔드 -> 백엔드:  [OPTION] method의 요청 전송 (preflight라고 하는, 예비 요청이다!)

2. 백엔드 처리

3. 백엔드 -> 프론트엔드: 1번 요청에 대한 응답

 

# 2. 실제 요청

4. 프론트엔드 -> 백엔드: [POST/GET/PUT, ...] method의 원하는 요청 전송    

   - Origin 헤더 필드에 출처(프론트엔드)추가해 전송

5. 백엔드 처리

6. 백엔드 -> 프론트엔드: 5번 요청에 대한 응답    

   - Access-Control-Allow-Origin 헤더 필드에 주소(4번 요청이 온 곳 = 프론트엔드) 추가해 전송

7. 프론트엔드: CORS정책에 위반되는지 확인 (4번 origin과 6번 origin 비교)

 

 

에서 백엔드에서 Access-Control-Allow-Origin 값을 붙여주지 않았기 때문에

4번 프론트엔드에서 CORS 정책이 위반되었는지 검사하는 과정에서 위반되었다고 판단한 것이다.

 

개발자도구

4번에서 react로 개발 시 자동으로 Origin이 붙어서 간다.

 

 

(참고로, localhost != 127.0.0.1 이다. 즉, 둘을 다른 출처라고 판단한다고 한다.

그 이유는 string value로 비교하기 때문이라고 아래 블로그에 잘 정리되어 있다)

https://www.datoybi.com/http-proxy-middleware/

 

 

CORS 관련하여 더 많은 내용은

 https://blog.areumsheep.vercel.app/contents/why-cors/ ,

https://aws.amazon.com/ko/what-is/cross-origin-resource-sharing/

https://developer.mozilla.org/ko/docs/Web/HTTP/CORS 세 블로그에 잘 정리되어 있다 ! 


 

시도 

1. react에 proxy 추가  (해결 x)

proxy를 두는 이유는, Frontend를 속여 Same Origin Policy를 달성하기 위해서이다.

만일 응답 받을 때의 출처를 동일하게 한다면

Same Origin Policy를 달성할 수 있기 때문에 (당연히) CORS 에러도 해결 가능하다.

 

그럼, Frontend와 Backend 사이에 어떤 서버가 존재하고, 그 서버가 수많은 Backend와 통신을 대행하면 어떨까?

그리고 그 대행 결과를 Frontend에서 받아오면?

결과적으로 Frontend는 하나의 Origin에서만 정보를 받아올 것이다.

즉, Same Origin Policy를 달성할 수 있는 것이다.

 

 

Proxy Server

 

 

 

이러한 방식을 적용해보자 !

$ npm install http-proxy-middleware

 

 

백엔드(http://localhost:8081)의 /pods URI로 라우팅 할 때 문제가 생기므로 해당 코드를 반영한다.

// src/setupProxy.js

const {createProxyMiddleware} = require('http-proxy-middleware')

module.exports = app => {
    app.use('/pods',
        createProxyMiddleware(
            {
                target: 'http://localhost:8081',
                changeOrigin: true,
            }
        )
    )
}

 

 

여전히 해결되지 않는다 ~~~~~

 

 


해결

2. go 웹 서버에 헤더 추가 (해결 o)

func (h *Handler) Run() {
	config := cors.Config{
		AllowOrigins: []string{"*"},
		AllowMethods: []string{"GET", "POST", "PUT", "DELETE"},
		AllowHeaders: []string{"Content-Type", "Authorization", "Origin"},
	}

    h.engine.Use(cors.New(config))

	// (생략)
}

Go 서버에 CORS 관련 규칙을 추가해줬다.

 

즉, 헤더에 Access-Control-Allow-Origin 설정을 추가하는 것인데, 

gin framework에서는 이와 관련된 처리를 간단하게 할 수 있도록 해준다.

 

AllowOrigins: []string{"*"}

이 부분은 Access-Control-Allow-Origin = "*" 과 같으며, 

모든 출처(Origin)에서 리소스에 접근 가능하다는 의미이다.

 

하지만 여전히 에러가 해결되지 않는다....

 

서칭을 하던 중, 아래 블로그가 아주 큰 도움을 주었다.

https://brownbears.tistory.com/337   

 

 

그렇다. 모든 라우트의 핸들러마다 헤더를 일일이 추가해야만 적용이 된다는 것이다.

이 방식대로 추가하니, 해결되었다 !!!!!

 

 


정리

SOP = client와 server가 같은 origin이어야 한다

CORS = client가 client와는 다른 origin으로부터 받을 수 있다. (헤더 추가)

 

SOP, CORS은 모두 client(프론트엔드) 측의 정책이다.

 

CORS에러가 일어났을 때 Access-Control-Allow-Origin 헤더를 확인해보자.

백엔드에서 이 헤더의 필드를 채우지 않았을 지 모른다. (*로 설정, 또는 프론트엔드의 url로 설정)


마치며

검색을 하니 아주 흔한 에러 중 하나인데, 아직 일일이 개발자가 신경써야 하는 부분이기에 신기했다.

뭔가 당연하게 개발 도구에서 지원해줄 줄 알았는데, 아무래도 보안과 관련된 문제가 그런 것일까?

아무튼 해결해서 기분이 좋다 ~!!!