Axios withCredentials 옵션
- information
- react
- spring-boot
이 글의 목차
1. Background
쿠키(cookie)와 세션(session)을 통한 인증 프로세스를 사용하는 서비스의 구조를 다음과 같이 바꾸면서 문제가 발생했다.
- 서비스 구조 변경 전 nginx를 웹 서버이자 포워드 프록시(forward proxy)로 사용
- 브라우저는 웹 서버와만 소통한다.
- 웹 화면은 웹 서버로부터 전달받는다.
- 데이터는 웹 서버를 통해 백엔드 서비스로부터 전달받는다.
- 브라우저는 백엔드 서비스의 존재를 모른다.
- 서비스 구조 변경 후 nginx를 웹 서버로만 사용
- 브라우저는 웹 서버와 백엔드 서비스와 모두 소통한다.
- 웹 화면은 웹 서버로부터 전달받는다.
- 데이터는 백엔드 서비스로부터 직접 전달받는다.
- 원장 리소스(origin resource)가 웹 서버인 상태로 백엔드 서비스와 통신하면서 CORS(cross origin resource sharing) 정책 위반이 발생한다.
2. Problem and Cause
다음과 같은 문제가 발생했다.
- 세션 생성시 발급된 세션 아이디(session id)가 담긴 쿠키가 브라우저로 전달되지 않는다.
- axios를 사용한 API 요청시 쿠키가 백엔드 서비스로 전달되지 않는다.
다음과 같은 원인들이 겹치면서 문제가 발생했다.
- nginx가 포워드 프록시로 사용되는 동안 클라이언트는 하나의 호스트를 통해서만 서비스 받는다.
- 하나의 호스트를 통해 서비스 받으므로 동일 사이트(same site) 조건을 항상 만족한다.
- 쿠키는 동일 사이트의 경우 브라우저에 의해 자동적으로 전달된다.
- nginx를 웹 서버로 사용하면서 브라우저가 백엔드 서비스와 직접 통신하게 된다.
- 크로스 사이트(cross site) 문제가 발생한다.
- 브라우저에 의해 쿠키가 자동으로 전달되지 않는다.
- axios는 크로스 사이트 요청 시 기본적으로 쿠키, 인증 헤더 혹은 TLS(Transport Layer Secure) 인증서 같은 자격 증명(credential)을 사용하지 않는다.
- 백엔드 서비스에 별도 설정이 없는 경우 크로스 오리진(cross origin) 문제가 발생한다.
3. withCredentials Property
The XMLHttpRequest.withCredentials property is a boolean value that indicates whether or not cross-site Access-Control requests should be made using credentials such as cookies, authorization headers or TLS client certificates. Setting withCredentials has no effect on same-origin requests.
withCredentials는 XMLHttpRequest 객체의 속성이다. 해당 속성을 통해 크로스 사이트로 요청 시 쿠키, 인증 헤더 혹은 TLS 클라이언트 인증서 같은 자격 증명을 사용할지 여부를 결정한다. 기본 값은 false이므로 크로스 사이트로 요청 시 쿠키를 함께 전달하지 않는다. 사용하기 쉬운 API를 제공하는 axios도 내부적으론 XMLHttpRequest 객체를 사용하기 때문에 withCredentials 관련 설정을 옵션을 통해 지정할 수 있다.
4. Practice
간단한 시나리오를 통해 관련 설정에 대해 알아보겠다. 컨테이너 없이 로컬 호스트에서 개발 서버를 띄우는 방식으로 실습을 진행한다.
- 다음과 같은 기술 스택을 사용하였다.
- 프론트엔드 서비스 - React
- 백엔드 서비스 - Spring Boot
- 서버는 세션을 통해 사용자의 상태를 유지한다.
- 세션에
foo문자열을 저장하고 매 요청마다-foo문자열을 저장된 값 뒤에 추가한다.
- 세션에
- 클라이언트는 axios를 사용해 서버로 직접 요청을 수행한다.
withCredentials를 true로 지정하면 쿠키를 통해 사용자 세션을 찾아 이를 재활용한다.withCredentials를 별도로 설정하지 않으면 쿠키를 함께 전달하지 않으므로 매번 새로운 세션을 생성한다.
4.1. Frontend Service
프론트엔드는 로컬 호스트(localhost) 3000 포트에서 서비스한다.
4.1.1. App.tsx
withCredentials버튼withCredentials설정을 true로 지정한다.- 백엔드 서비스로 직접 요청을 보낸다.
withoutCredentials버튼withCredentials설정이 없으므로 기본 값인 false가 적용된다.- 백엔드 서비스로 직접 요청을 보낸다.
import React, { useState } from "react";
import axios from "axios";
function App() {
const [message, setMessage] = useState<string>("");
const successHandler = ({ data }: { data: any }) => {
setMessage(data);
return data;
};
const errorHandler = ({ message }: { message: string }) => {
setMessage(message);
};
const withCredentialsHandler = () => {
axios
.get("http://localhost:8080/foo", { withCredentials: true })
.then(successHandler)
.catch(errorHandler);
};
const withoutCredentialsHandler = () => {
axios
.get("http://localhost:8080/foo")
.then(successHandler)
.catch(errorHandler);
};
return (
<div>
<div>
<button onClick={withCredentialsHandler}>withCredentials</button>
<button onClick={withoutCredentialsHandler}>withoutCredentials</button>
</div>
<p>{message}</p>
</div>
);
}
export default App;
4.2. Backend Service
백엔드는 로컬 호스트 8080 포트에서 서비스한다.
4.2.1. FooController Class
- 요청 객체에서 세션 객체를 획득한다.
- 쿠키에 세션 ID가 있는 경우 이전에 생성한 세션을 획득한다.
- 쿠키에 세션 ID가 없다면 신규 세션을 생성한다.
- 세션에 저장된 값이 없다면 기본 값을 세션에 저장하고 반환한다.
- 세션에 저장된 값이 있다면 이에
-foo문자열을 더하여 저장하고 이를 반환한다.
package action.in.blog.controller;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class FooController {
private static final String key = "foo";
@GetMapping("/foo")
public String foo(HttpServletRequest request) {
String defaultValue = "foo";
HttpSession session = request.getSession();
String fooInSession = (String) session.getAttribute(key);
if (fooInSession == null) {
session.setAttribute(key, defaultValue);
return defaultValue;
}
String result = fooInSession.concat("-").concat(defaultValue);
session.setAttribute(key, result);
return result;
}
}
4.2.2. WebConfig Class
백엔드 서비스도 자격 증명 관련 설정이 필요하다. WebMvcConfigurer 인터페이스 구현을 통해 관련된 설정을 추가한다.
- 다음과 같은 경우에 CORS 정책을 허용한다.
- 모든 경로
- 오리진(origin) 서버가
localhost:3000인 경우 - GET 메서드
allowCredentials메서드를 통해 자격 증명을 허용한다.- 해당 설정이 true인 경우 응답 헤더에
Access-Control-Allow-Credentials값이 실려 브라우저에게 전달된다. - 해당 설정이 하지 않거나 false인 경우
- 자격 증명을 함께 전달한 요청은 브라우저에서 CORS 정책 위반 에러가 발생한다.
- 자격 증명을 함께 전달하지 않은 요청은 브라우저에서 정상 처리된다.
- 해당 설정이 true인 경우 응답 헤더에
package action.in.blog.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("http://localhost:3000")
.allowedMethods("GET")
.allowCredentials(false)
;
}
}
Result without allowing credential
- 백엔드 서비스에서 자격 증명 허용을 하지 않은 경우이다.
- 브라우저에서 자격 증명을 함께 전달하는 경우 CORS 정책 위반 에러가 발생한다.
- 브라우저에서 자격 증명을 전달하지 않은 경우 정상 동작하지만, 쿠키가 생성되지 않는다.
Result with allowing credential
- 백엔드 서비스에서 자격 증명을 허용한 경우이다.
- 브라우저에서 자격 증명을 함께 전달하는 경우 정상 동작하고 세션 ID가 담긴 쿠키가 생성된다.
- 브라우저에서 자격 증명을 전달하지 않은 경우 정상 동작하지만, 쿠키가 생성되지 않는다.
CLOSING
다음 코드를 통해 axios의 withCredentials 설정을 전역으로 지정할 수 있다.
axios.defaults.withCredentials = true;
TEST CODE REPOSITORY
REFERENCE
광고
KEEP READING
함께 읽으면 좋은 글
DISCUSSION