본문 바로가기
  • 🦄 창민이 개발일지
서버

[BUCL 프로젝트] 프론트 서버와 API 서버를 분리로 인한 CORS 문제 및 로그인 갱신 문제 해결

by 창민이 개발일지 2024. 5. 24.

BUCL 쇼핑몰 프로젝트를 진행하면서 클라이언트와 서버 연결할 때 CORS 문제가 발생 했고, 또한 CORS로 인해 로그인 갱신 문제도 발생했습니다.

1. CORS 문제 원인 및 해결

오류 원인: 서브 도메인이 달라 CORS 발생

S3로 프론트 서버 구축, EC2에는 API 서버를 구축하게 되었는 데, 이로 인해 서브 도메인을 기준으로 두개의 URL를 만들어지 게 되고 그로인해 출처가 달라지게 되어 CORS 문제가 발생하게 되었습니다.

서브도메인이 다른 경우

CORS 에러는 웹 개발을 할때도 자주 발생하는 문제 중 하나 인데, 출처 즉 도메인이 다른 경우 브라우저가  XSS(Cross-Site Scripting) 같은 보안 문제를 자동으로 막게 됩니다. 

 

CORS란

CORS는 Cross Origin Resource Sharing의 약자로, 한국말로 교차 출처 리소스 라고 합니다. 여기서 Origin을 도메인으로 바꾸면 좀 더 이해가 가는 데, CORS는 한 도메인이 다른 도메인 간의 요청을 할 수 있도록 하는 메커니즘으로 거의 모든 브라우저에서는 도일 출처 정책 즉 no-cors 정책으로 되어 있기 때문에 도메인 즉 url이 다르면 요청 자체를 거부 하도록 되어 있습니다.

과거에는 웹과 서버를 동시에 개발해서 cors가 거의 발생하지 않았는 데, 요새는 React 같이 웹과 서버를 나눠서 개발하고 연결하기 때문에  도메인이 다른 경우가 많이 발생해서 CORS도 자주 발생합니다. 위에서 소개 된 출처에 대해서 밑에서 더 정확하게 설명하겠습니다.

 

출처(Origin)

출처를 설명하기 전에 URL의 구조를 보면 다음과 같습니다.

 

URL안에는 어떤 방식으로 통신할 지를 나타내는 Protocol, 서버의 주소를 나타내는 Host, 컴퓨터의 폴더의 위치와 같이 서버의 자원의 위치를 나타내는 Path, 서버에 데이터를 보낼때 사용하는 Query Parameter(String), 웹 컴포넌트의 id 위치로 이동할 수 있는 Fragment 그리고 위의 그림에는 없지만 해당 주소의 어떤 경로로 들어갈 지 지정하는 포트로 구성 되어 있습니다. 

여기서 출처는 Protocol, Host, 포트번호를 의미합니다.



따라서 origin이 다르다는 소리는 Protocol, Host, 포트번호가 하나라도 다르면 origin이 다릅니다. 따라서 Protocol, Host, Port Number만 같으면 똑같은 출처를 의미하게 됩니다.

 

출처 비교와 차단: 브라우저가 처리

출처를 비교하는 로직은 서버에서 처리하지 않고 브라우저에서 처리 됩니다. 서버는 리소스 요청에 의한 응답을 성공적으로 보내지만 브라우저가 이 응답을 분석해서 동일 출처가 아니면, CORS 에러를 발생시킵니다. 위와 같은 문제는 결국 서버가 보낸 응답 헤더에 정보를 덜 주었기 때문에 발생하게 됩니다.

 

동일 출처 정책 (SOP: Same-Origin Policy)

SOP(Same Origin Policy) 정책은 이름 그대로 동일한 출처에 대한 정책을 의미합니다. 이 정책은 동일한 출처에서만 리소스 공유가 가능하다라는 법률을 가지고 있습니다. 즉, 동일 출처(Same-Origin) 서버에 있는 리소스는 자유로이 가져올 수 있지만, 다른 출처(Cross-Origin) 서버에 있는 자료는 가져올 수 없습니다. 브라우저는 SOP 정책을 따르기 때문에 출처가 다른 경우에 CORS 에러를 발생 시킵니다. 

BUCL 쇼핑몰의 경우도 프론트 서버, API 서버의 출처가 다르기 때문에 CORS 에러를 발생하게 됩니다.

SOP의 경우 CORS 정책을 지킨 리소스 요청에 대해서는 요청을 차단하지 않습니다.

 

브라우저의 CORS  동작 과정

1. 클라이언트에서 HTTP 요청의 헤더에 Origin을 담아 전달

 

2. 서버는 응답헤더에 Access-Control-Allow-Origin을 담아 클라이언트로 전달

3. 클라이언트에서 Origin과 서버로 보내준 Access-Control-Allow-Origin을 비교합니다.

 

문제 해결

문제 분석: 서버에 CORS 관련 설정이 되어 있지 않습니다.

Spring SecurityConfig 컴포넌트에 CORS 설정

 

문제 해결

2. 로그인 갱신(Access Token 값 갱신) 되지 않는 문제 발견

로그인 Access token 갱신 테스트 중 값이 갱신 되지 않는 문제를 발견 했는 데, 브라우저의 쿠키 저장소를 본 결과 refresh token이 저장되지 않았습니다.

 

서버의 응답값에 refresh 쿠키가 잘 전달 됐는 지 확인

서버에서는 refresh token 쿠키를 포함해서 보낸 걸 확인 할 수 있습니다. 따라서 서버 문제가 아니라 클라이언트 문제라는 확인할 수 있는 데요.

문제 원인 분석 결과 서로 다른 도메인(크로스 도메인)에 요청을 보낼 때 요청에 credentials 정보를 담아서 보내야 됩니다.

 

Credentials

Credentials 이란 쿠키, Authorizatoin 인증 헤더, TLS client Certificates(증명서)를 내포하는 자격 인증 정보를 말합니다.

기본적으로 브라우저가 제공하는 요청 API들은 별도의 옵션 없이 브라우저의 쿠키와 같은 인증과 관련된 데이터를 함부로 요청 데이터에 담지 않도록 되어 있습니다. 이는 응답을 받을 때도 마찬가지입니다.

 

withCredentials 

withCredentials 옵션은 다른 도메인(크로스 도메인)에 요청을 보낼 때 요청에 인증 정보(Credentials)를 담아서 보낼 지 결정하는 항목입니다.

즉 쿠키를 포함시켜 요청하고 싶으면, 클라이언트의 API 요청 메소드에 withCredentials 옵션을 true로 설정해주어야 됩니다.

export const privateApi = axios.create({
  baseURL: process.env.REACT_APP_BASE_URL,
  headers: {
    'Content-Type': 'application/json; charset=UTF-8',
    Accept: 'application/json',
  },
  withCredentials: true,
});

또한 서버에서도 Access-Control-Allow-Credentials 헤더를 true로 해주어야 됩니다.

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    return http
        ...
        .cors(corsCustomizer -> corsCustomizer.configurationSource(request -> {
            ...
            config.setAllowCredentials(true);
            ...
        }))

 

로그인 Access-Token 갱신 문제 해결