내가 만난 CORS 관련 문제

우테코에서 미션을 진행하면서 아니 왜 스프링 인터셉터에서 토큰을 검증하니 CORS 에러가 뜨면서 로그인이 안되는 거지? 이번에 사이드 프로젝트를 진행하면서 아니 왜 JsessionID가 쿠키로 안 들어오는거야? CORS와 엮이면 아니 왜부터 튀어나오게 되는데.. 이번에 정리해보자

 

CORS

SOP

  • 브라우저는 스크립트에서 생성된 HTTP 요청에 SOP를 적용한다.
  • SOP란 다른 출처의 리소스를 사용하는 것을 제한 하는 보안 방식이다.
  • URL의 프로토콜, Host, Posrt를 통해 같은 출처인지 다른 출처인지 판단 할 수 있다.

CORS

  • 추가적인 HTTP header를 사용해서 애플리케이션과 다른 Orgin의 리소스에 접근할 수 있도록 하는 매커니즘

 

문제적 상황 1

프론트 엔드 애플리케이션  (localhost:8081)과 서버 애플리케이션 (localhost:8080)을 사용하는 상황이다. 둘은 다른 Orgin이다. 프론트 엔드 측에서 axios를 사용하여 HTTP요청을 보내고 있다. 스크립트에서 생성된 HTTP요청은 SOP가 적용된다. CORS가 필요한 상황인 것이다. 그래서 우리는 아래와 같이 모든 요청 URL에 대해 모든 Method와 모든 Orgin을 허락해 주었다.

 

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**").allowedMethods("*").allowedOriginPatterns("*");
    }
}

 

여기까지는 OK.

 

인증 토큰에 관한 검증 처리를 Intercepter에서 하도록 변경을 하였다. 그랬더니 로그인이 안되는 상황이 발생한다. CORS...!

 

응답을 보니 로그인을 잘되었다. 

 

 

문제는 로그인해서 받아온 토큰을 사용하는 members/me에서 발생한다. 로그인해서 받아온 토큰을 통해 user 정보를 얻어와야 하는데 GET이 아닌 OPTION메소드를 사용하고 있다.

 

 

해당 요청에 대한 응답을 Access-Control-Allow-Origin에 프론트 엔드 애플리케이션 origin이 허락되어 있고, 모든 메소드(GET, HEAD, POST, PUT...)에 대한 허락도 되어있다. 

 

 

문제의 원인 Preflight

Preflight

  • Prefilght Request는 실제 요청 전에 인증 헤더를 전송하여 서버의 허용 여부를 미리 체크하는 테스트 요청이다.
  • Preflight는 OPTIONS 메소드를 사용한다.
  • 아래는 Prefilght Request의 본문이다.
  • Acess-Contol-Request-Method
    • 실제 요청의 메소드
    • 토큰을 사용해서 멤버 정보를 얻어오려는 GET요청을 보내려고 했었다.
  • Acess-Contol-Request-Header
    • 토큰을 authorization 헤더에 담아 보내려고 했었다.

 

 

그래서?

  • 브라우저는 preflight 요청에 대한 응답이 200이어야 본 요청을 수행한다.
    • 그런데 500이잖아..!

서버에서는 member/me로 들어온 요청에 대해 토큰 검사를 하고 있다. 그러나 member/me preflight의 authorization에는 토큰이 들어 있지 않다. 서버에서 모든 origin에 대해 허가를 해뒀음으로 preflight요청에 대한 응답헤더는 정상적으로 작성이 되었으나 intercepter에서 토큰을 처리하다가 500에러를 발생시키므로 응답 StatusCode로 500이 들어가게 되었다. 그래서 브라우저는 이를 CORS에러로 판단한 것이다.

 

해결방안

  • intercepter에서 OPTION요청을 분기 처리 하기
 if (StringUtils.equals(request.getMethod(), "OPTIONS")) {
            return true;
 }

 

문제적 상황 2

이번에 사이드 프로젝트를 진행했다. 기존에는 같은 origin의 클라이언트를 사용하였다. 이것을 분리하고자 리액트로 프론트엔드 애플리케이션을 (localhost:3000) 만들었다. 인증은 세션으로 처리했는데, 애플리케이션을 다른 origin으로 분리하고 나니 클라이언트의 요청에 자동 JsessionID가 들어가지 않는다.

 

문제의 원인 Credentialed Request

Credentialed Request

  • 인증 관련 헤더를 포함 할 때 사용하는 요청
  • 구글 크롬 브라우저의 credential의 기본값은 같은 출처 내에서만 인증 정보를 사용하겠다는 same-origin

해결방안

  • 서버에서 "Acess-Control-Allow-Credentials" 헤더를 true로 설정 
@Configuration
class WebConfig : WebMvcConfigurer {
    override fun addCorsMappings(registry: CorsRegistry) {
        registry.addMapping("/**").allowCredentials(true)
    }
}
  • 클라이언트에서 withCredential 속성으로 요청 전달 (axios 기준)
    createRoom(roomInfo) {
        return axios.post(BASE_URL + 'rooms', roomInfo, { withCredentials: true });
    }

 

여담으로 localhost:8080의 JsessionID는 잘 저장이 되는데 localhost:3000의 JsessionID는 저장 조차 안되었다. 둘 다 set-cookie로 JsessionID를 받는데 의아했었다. 그러나 이 문제는 localhost의 쿠키를 모두 지워주고 localhost:3000으로 다시 테스트 하니 해결 되었다. 다른 도메인 간 세션 중복과 같은 키워드로 검색해 보면 될 것 같다. 

 

참고

 

[React] axios 의 withCredentials

django 와 react 를 통해 프로젝트를 진행하고 있는데 두 서버를 연결하는데 있어서 몇 가지 문제점이 발생하였습니다. django CORS [Django] CORS, Cross-Origin Resource Sharing CORS 란? 제목에서 알 수 있듯..

ssungkang.tistory.com

 

 

 

'study' 카테고리의 다른 글

[AWS] AWS에는 무슨 서비스가 있을까?  (0) 2021.07.20
[java]JVM JRE JDK  (0) 2021.06.17
[java] 클래스와 인스턴스  (0) 2021.06.10
[JAVA] Optional  (0) 2021.05.15
Spring DI  (0) 2021.04.30

댓글



Designed by JB FACTORY