처음으로 프론트와 협업중에서 Contorller의 구현이 끝나서 확인하기 위해서 배포를 진행했습니다.

배포를 진행해서 확인을 하려고하는데 프론트에서 다음사진과 같은 문제 때문에 값이 안받아진다고 했습니다...

프론트에서 CORS 오류...

아니 도대체 CORS가 뭔데 오류인건지 확인을 해보니 저 뿐만이 아닌 모든 프론트&백엔드 개발자가 겪는 오류여서 무엇이 문제인지 CORS가 무엇인지 정리하기 위해서 글을 작성해봅니다.


CORS란

CORS(Cross-Origin Resource Sharing)은 언제발생하냐면 내 출처가 아닌 다른 출처에서 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에게 알려주는 체제라고합니다.

뭔 소리인지 자세하게 알아보겠습니다. 우선 내 출처가 아닌 다른 출처 라는 것을 확인해보겠습니다.

만약, http://chan-coding-book.com:8080 이라는 출처가 바로 내 출처라고 생각해봅시다.

uri를 구성하는 요소는 다양하게 존재합니다 포트번호, 도메인, 프로토콜 등이 존재합니다.

  • 포트번호 : 8080
  • 도메인 : chan-coding-book.com
  • 프로토콜 : http

가 다음과 같이 존재를 합니다.

내 출처는 바로 포트번호, 도메인, 프로토콜이 같은 경우를 말합니다.

우리는 SOP(Same-Origin Policy)라는 규칙때문에 같은 출처에서만 리소스를 공유할 수 있습니다.

그렇기 때문에 내 출처가 아닌 다른 출처에서 리소스를 공유할 때는 CORS가 발생합니다.

물론 동일 출처(내 출처)인 경우는 SOP 덕에 리소스를 공유를 할 수 있습니다.

왜 막아두냐하면 CSRF 공격 때문에 그렇습니다.

해커가 마음대로 API를 통해서 웹사이트로 개인 정보를 탈취할 수 있기 때문에 SOP가 존재하는 이유입니다.


문제 발생 이유

그렇기 때문에 문제가 발생한 이유는 현재 프론트는 localhost:3000을 사용하지만 우리는 전혀 다른 도메인과 프로토콜을 사용하고있습니다.

그래서 출처가 달라져서 CORS 문제가 발생했습니다.

이제 그래서 CORS는 어떻게 동작하는지 살펴보겠습니다.


CORS의 동작원리

CORS는 동작이 3가지로 나누어집니다. Preflight requestSimple request, Credentialed Request입니다.

현재 오류코드를 다시 본다면 Preflight request에서 오류가 나왔다고 합니다.

우선 Preflight request가 무엇인지 먼저 살펴보겠습니다.


Preflight Request

Preflight라는 말을 이해하면 이해하기가 쉬울것 같습니다.

Pre는 먼저라는 소리이고 flight는 날아간다는 소리이기 때문에 먼저 날려본다라고 해석하면 좋을것 같습니다.

그래서 확인을 해보자면 우리가 다른 도메인의 리소스로 OPTIONS 메서드를 사용해서 HTTP 요청을 보내서 실제 요청이 전송하기에 안전한지 확인하는 것입니다.

왜냐하면 Cross-origin 요청은 유저 데이터에 영향을 줄 수 있기 때문입니다.

우리가 오류가 생긴이유는 먼저 보내봤는데 오류가 나왔기 때문에 그렇습니다.

Preflight request가 요청된 예제를 한번 살펴봅시다.

OPTIONS /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Origin: http://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type


HTTP/1.1 204 No Content
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive

Request를 하나하나 확인해 봅시다. 우선, OPTIONS 이라는 메서드는 서버에서 추가 정보를 판별하는 메서드라고 합니다

이 메서드는 2개의 다른 요청 헤더가 전송이 됩니다. 헤더는 다음과 같습니다.

Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type

그렇기 때문에 해석을 한번 해봅시다.

  • Access-Control-Request-MethodPOST로 실제 요청을 전송한다는 뜻입니다.
  • Access-Control-Request-HeadersX-PINGOTHERContent-Type이 전송이 된다는 뜻입니다.

그래서 결론적으로는 요청을 전송할 때 POST 메서드와 요청 헤더를 X-PINGOTHER으로 받을 수 있다는 것을 의미합니다,

나머지 것들도 살펴보도록 하겠습니다. 이 값들을 이해해야 Spring에서 설정을 할 수 있습니다.

Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400

Access-Control-Allow 라는 뜻은 직독하면 접근이 허용된것이라고 생각하면 됩니다. 그래서 결론은 다음과 같습니다.

  • Originhttp://foo.example 만 허용한다고 보입니다.
  • MethodPOST, GET, OPTIONS 만 허용하는 것을 볼 수 있습니다.
  • HeadersX-PINGOTHER, Content-Type 만 허용하는 것을 볼 수 있습니다.
  • Max-Age는 최대 캐싱 시간입니다. 86400이므로 24시간을 뜻합니다. 이는 숫자가 클수록 우선순위가 높습니다.

우리는 이러한 preflight request에서 오류가 나왔기 때문에 이를 spring security의 설정을 통해서 해결해보도록 합시다.


해결방안

우선 이러한 문제를 해결하기 위해서 WebSecurityConfigureAdapter 를 상속받는 WebSecurityConfig 라는 클래스를 만들었습니다.

@Configuration // 설정파일 등록
@EnableWebSecurity // 웹보안 활성화
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //...
        http.cors();
    }
}

그리고 다음과 같이 cofigure(HttpSecurity http) 라는 메서드를 오버라이드 합니다. 또한, 설정에 대한 @Configuration@EnableWebSecurity에 대한 애너테이션은 꼭 붙여야합니다.

이후에 http.cors() 라는 메서드를 호출합니다.

하지만 이렇게 설정을해도 계속 오류는 발생합니다. 이러한 오류를 해결하기 위해서 Access-Control에 대한 범위를 설정을 해줍니다.

설정을 하기 위해서 WebMvcConfigure 를 구현하는 WebConfig라는 클래스를 다음과 같이 만들것 입니다.

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry){
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowedMethods("*")
                .allowedHeaders("*")
                .allowCredentials(false);
    }
}

그 후 addCorsMappings(CorsRegistry registry) 메서드를 오버라이딩 합니다.

이후에 registry에 대해 허용을 설정을 하나씩 합니다.

  • addMapping 을 통해서 어떤 주소에 대해서 설정할지를 결정합니다. "/**"를 명시하면 모든 경로에 대해서 설정입니다.
  • allowedOrigins 를 통해서 허용할 Origin에 대해서 설정합니다.
  • allowedMethod 를 통해서 허용할 메서드를 설정합니다.
  • allowedHeders 를 통해서 허용할 Header들을 설정합니다.
  • allowCredentialsfalse로 설정해야 Origins*이라는 와일드 카드가 들어갈 수 있습니다.

다음과 같은 설정을 한다면 해결이 될 것입니다.


Reference

'Spring > 디버깅' 카테고리의 다른 글

Json이 LocalDateTime에서 잘못 넘겨지는 경우  (0) 2022.02.23

스프링 프로젝트를 만드는 도중에 LocalDateTime 이라는 항목에 대해서 Json으로 반환을 할때 다음 사진과 같이 넘겨지는 오류를 발견했습니다.

image


원래는 다음 그림과 같이 날짜와 시간등이 변환이 되어야합니다.

image

문제점을 알기위해서 확인해보니 다음과 같은 방식으로 해결할 수 있었습니다.

해결방법

기존 LocalDateTime 을 넘겨주는 DTO는 다음과 같은 인스턴스 변수로 생성되어 있습니다.

private LocalDateTime createAt;

이러한 코드에서 @JsonFormat을 통해서 패턴을 넣어서 String 형으로 다시 반환을 할 수 있게 할 수 있습니다.
코드는 다음과 같습니다.

@JsonFormat(shape= JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS")
private LocalDateTime createAt;

그러면 해결이 되는 것을 볼 수 있습니다.

'Spring > 디버깅' 카테고리의 다른 글

[Spring/오류수정] CORS(Cross-Origin Resource Sharing)  (0) 2022.02.25

+ Recent posts