개발 노트/에러 화풀이 기록

[Spring] 스프링 - 리액트 연동 과정에서 생긴 CROS 에러

hectick 2022. 9. 1. 11:25

프론트엔드(리액트)와 백엔드(스프링)을 연동하는 과정에서 아래와 같은 에러가 났다.

 

프론트엔드에서 백엔드 서버로 요청을 보냈을때 발생한 에러의 사진

 

 

나는 스프링을 이용해 백엔드 서버를 하고 있고, 친구가 리액트를 이용해 프론트엔드를 맡고 있다.

 

로컬 환경에서 postman을 이용해 응답 테스트를 할 때는 잘 돌아갔다.

AWS에서 서버를 만들어서 톰캣서버를 만들고 프로젝트를 올려 postman을 이용해 응답 테스트를 할 때도 잘 돌아갔다.

크롬에 새 창을 켜서 url로 get요청을 보내면 잘 작동한다.

 

그러나!!!

근데 리액트랑 연동만 하니 CROS 에러가 나타났다.

CORS란? Cross-Origin Resource Sharing(교차 출처 리소스 공유)의 약자이다.

정확히 CORS가 뭔지는, 프로젝트가 끝난 후 시간이 생겼을때 공부해서 포스팅을 하도록 하겠다.(추후를 기약)

 

위의 에러를 보면 CORS 정책에 의해서 요청이 막혔고, 'Access-Control-Allow-Origin' 헤더가 요청 리소스에 없다고 나온다. 

 

9월 1일 목요일

구글링을 해보니 CORS... 진짜 골치아픈 놈 같다.

일단 인터넷에 나온방법 세개 써봤는데 안된다.

다음주에 다시 도전한다!

 

일단 쓴 방법

1. @CrossOrigin 어노테이션 사용 -> cros 에러는 발생하지 않으나 쿠키가 프론트로 전달이 안되는 듯 하다.

2. WebMVCConfigurer  -> net:error_failed

3. filter로 헤더 세팅하기 -> net:error_failed

*모든 과정에서, 프론트에서는 withCredential 옵션을 true로 설정해줬다.

 

9월 2일 금요일

구글링하다가 다른 형태의 filter를 찾아서 적용을 해봤는데 잘 되어서 해결방법을 소개한다.

 

해결방법:  https://sangjin0309.tistory.com/10 에서 발견한 코드를 토대로 약간의 수정을 하여 아래와 같은 새로운 코드를 추가하였다. CorsFilter.java라는 새로운 파일을 만들어서 아래의 코드를 붙여넣어 준다.

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;

public class CorsFilter implements Filter {
	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { 
		HttpServletResponse response = (HttpServletResponse) res; 
		response.setHeader("Access-Control-Allow-Origin", "http://localhost:3000"); 
		response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTION"); 
		response.setHeader("Access-Control-Allow-Credentials", "true");
		response.setHeader("Access-Control-Max-Age", "3600"); 
		response.setHeader("Access-Control-Allow-Headers", "x-requested-with, origin, content-type, accept"); 
		chain.doFilter(req, res); 
		} 
	public void init(FilterConfig filterConfig) {} 
	public void destroy() {}
}

위의 코드에서 중요한 것은,

"Access-Control-Allow-Origin"를 "http://localhost:3000"로 셋팅하는 것

    -> 바로 아랫 줄의 withCredential : true 옵션을 쓰려면 *로 쓰지말고 주소를 특정해줘야 한단다

"Access-Control-Allow-Credentials"를 "true"로 셋팅하는 것

    -> 백과 프론트 양쪽에 설정해야 쿠키를 주고 받을 수 있단다

"Access-Control-Allow-Methods"에 사용하는 http 메소드와 OPTION 메소드를 추가하는 것

    -> preflight인지 뭔지 하는 과정에서 OPTION 메소드를 이용해 확인한단다

 

 

추가로 web.xml 파일에 아래와 같은 필터도 넣어준다.

<filter>
	<filter-name>CorsFilter</filter-name>
	<filter-class>{패키지 경로}.CorsFilter</filter-class>
</filter>
<filter-mapping>
	<filter-name>CorsFilter</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>

 

CORS 에러는 해결!

그러나 또 다른 난관이 있었으니...

바로 프론트 쪽에서 쿠키를 받고 이를 저장할 수 있었으나, 이 쿠키가 다음 요청에서 백엔드로 다시 보내내지가 않았다!!! 

 

이날, 프론트를 맡은 친구와 6시간의 삽질을 하고 알게 된 바는, 바로 크롬 정책중에 samesite 정책 때문이란다.

크롬의 SameSite = lax 라는 값 때문에 교차 출처로 쿠키가 전송이 안되는 것이었다.

이를 해결하려면 옵션을 변경하기도 해야하고, http://를 https://로 바꿔야 하는데, 이 과정에서 인증서도 발급받고... 아무튼 뭐가 되게 복잡해 보였다...

 

그래서 우리는 결국,

프론트에서 Proxy를 쓰기로 하였다^^

 

프록시 서버는 구글링하면 나오는 CORS 이슈를 해결하는 방법 중 하나이다.

프록시 서버란? 클라이언트에서 서버로 접속을 할 때, 직접적으로 접속하지 않고 중간에 대신 전달해주는 서버이다.

프록시 서버에 대해서도 나중에 시간나면 차차 포스팅 해보겠다^^

아무튼 이 서버를 백엔드 서버주소와 같게 하였더니 해결!

 

그럼에도 불구하고 아직 해결되지 않은 문제가 있었는데,

프록시 서버를 썼음에도 404에러가 나는 것. 분명히 맞는 url로 요청을 보냈는데 이상하다.

그래서 둘이 잠들기 직전까지 또 삽질을 했는데, 프론트 친구가 구글링해서 다음 내용을 봤다고 했다.

간단히 말하면,

프록시 서버를 설정하는 과정에서 내가 모든 요청 url을 http://{서버주소}:8080/api/~ 로 시작할 수 있게 @RequestMapping(value="/api")를 모든 controller 클래스의 제일 위에 달아주었는데,

/api로 시작하지 않는 경로에 대한 처리가 없을 경우 404 에러가 뜨기도 한다는 내용이다. 

 

아무튼, 나는 프로젝트에 http://{서버주소}:8080/ 으로 get요청이 들어왔을때 간단한 응답을 반환하는 코드를 작성해서 해결봤다.

 

야호!!!😂😁😂😁😂😁😂😁😂