2025. 2. 14. 13:40ㆍ백엔드
프론트, 백엔드 회원가입 기능을 구현하고 회원가입 버튼을 눌렀을 때 아래와 같은 CORS 에러가 발생했다.
그래서 이제 그 오류를 하나하나 해결해 보려고 한다.
signup:1 Access to XMLHttpRequest at 'http://localhost:8080/api/auth' from origin 'http://localhost:5173' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
방법 1. 리액트 auth.ts파일의 백엔드 기본 URL 설정 확인
원래는 아래와 같이 설정되어있었는데, 백엔드가 없을 때 임의로 넣어둔 URL이였다.
백엔드를 만들고 변경해줬어야했는데 안해준탓인 것 같아 컨트롤러에 매핑된 URL로 변경해주었다.
수정 전
const API_BASE_URL = 'http://localhost:8080/api/auth';
수정 후
// 변경
const API_BASE_URL = 'http://localhost:8080/api/v1/user';
하지만 에러는 해결되지 않았다 😂
방법 2. 스프링부트의 CorsConfig 파일 확인
CorsConfig 파일에 적어뒀던 설정이 잘못되었다고 하여 수정해주었다.
이전에 내가 작성한 리액트 - 스프링부트 연결하기 글에서와 같이 addMappring, allowedOrigins 만 설정해두었었다.
그런데 이번에 내가 할 프로젝트에서는 allowedMethods, allowedHeaders, allowCredentials 설정도 추가해주었다.
📌 allowedMethods("*")는 왜 필요할까?
요청이 비표준 헤더를 포함할 경우 (Authorization, Content-Type: application/json 등) 이거나 요청이 쿠키나 인증 정보를 포함할 경우에는 설정해주어야 한다.
📌 2. allowedHeaders("*")는 왜 필요할까?
CORS 요청에 포함된 특정 헤더(Authorization, Content-Type, X-Custom-Header 등)가 차단될 수 있다.
이 때, allowedHeaders("*")를 설정하면, 모든 요청 헤더를 허용할 수 있다.
📌 3. allowCredentials(true)는 왜 필요할까?
이 옵션을 설정해야 쿠키, 세션, 인증 헤더(예: JWT 토큰)가 포함된 요청이 가능해지기 때문에, 회원가입 또는 로그인 과정에서 필수로 설정되어야 한다.
수정 전
@Configuration
public class CorsConfig {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // 모든 경로에 대해
.allowedOrigins("http://localhost:5173"); // 프론트엔드 주소 허용
}
};
}
}
수정 후
@Configuration
public class CorsConfig {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // 모든 경로에 대해
.allowedOrigins("http://localhost:5173") // 프론트엔드 주소 허용
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 허용할 HTTP 메서드
.allowedHeaders("*") // 모든 헤더 허용
.allowCredentials(true); // 인증 정보 포함 허용
}
};
}
}
하지만 이번에도 해결되지 않았다 😫
방법 3. 프론트엔드(React) 요청 수정
다시 API를 설정해놓은 auth.ts 파일에 가서 withCredentials: true 를 추가해준다.
이건 백엔드 CORS 정책에서 allowCredentials(true)를 사용하면 반드시 설정해주어야 하는 코드이다.
서버에 JSON 형식의 데이터를 보낸다는 것을 명확히 지정하기 위해서 headers: { "Content-Type": "application/json" } 부분도 추가해주었다.
클라이언트에서 데이터를 서버(Spring Boot)로 보낼 때, HTTP 요청의 Content-Type 헤더를 명시적으로 application/json으로 설정해야 백엔드가 JSON 데이터를 올바르게 해석할 수 있다. 스프링부트가 JSON 데이터라고 인식하고 @RequestBody로 올바르게 매핑한다.
수정 전
export async function signupUser(
email: string,
password: string
): Promise<signupResponse> {
try {
console.log("여기");
const response = await axios.post(`${API_BASE_URL}/signup`,
{ email, password },
);
return response.data;
} catch(error) {
// throw new Error(errer.response?.data?.message || "회원가입 실패");
throw new Error( "회원가입 실패");
}
};
수정 후
export async function signupUser(
email: string,
password: string
): Promise<signupResponse> {
try {
console.log("여기");
const response = await axios.post(`${API_BASE_URL}/signup`,
{ email, password },
{
withCredentials: true,
headers: {
"Content-Type": "application/json",
},
}
);
return response.data;
} catch(error) {
// throw new Error(errer.response?.data?.message || "회원가입 실패");
throw new Error( "회원가입 실패");
}
};
아니 이번에도 해결되지 않았다 😭
방법 4. CORS preflight request 테스트 후 spring-security-crypto 라이브러리 설치
이번에는 CORS preflight request을 테스트하는 명령어를 통해서 응답헤더를 확인해 보았다.
curl -X OPTIONS http://localhost:8080/api/v1/user/signup -i
위 명령어를 스프링부트의 터미널에 입력해보자.
추가 설명을 해보자면 이 명령어를 실행하는 이유는 서버가 CORS 요청을 올바르게 처리하는지 확인하는지 확인 하기 위해서이다.
HTTP/1.1 401
WWW-Authenticate: Basic realm="Realm"
...
헉 정상적인 명령이라면 200 OK 이가 떠야하지만 나는 401이 떴다!
이건 CORS 오류가 아닌 인증오류가 나고 있다는 뜻
WWW-Authenticate: Basic realm="Realm" → 기본 인증(Basic Auth)이 요구되고 있다.
나는 회원가입할 때 비밀번호를 암호화하기 위해 PasswordEncoder 인터페이스를 사용했고
해당 인터페이스를 사용하기 위해 build.gradle에 스프링 시큐리티 의존성을 주입해주었다.
그래서 스프링 시큐리티의 기본 인증이 필요해졌고 인증과정을 설정해주지 않은 나는 그대로 오류가 났던것!
처음에는 build.gradle에서 스프링 시큐리티 의존성을 아예 삭제했었는데 그러면 또 PasswordEncoder 부분이 에러가 났다.
그래서 스프링 시큐리티는 사용하지않으면서 PasswordEncoder 만 사용가능한 의존성을 주입해주기로 했다.
// 수정 전
implementation 'org.springframework.boot:spring-boot-starter-security'
// 수정 후
implementation 'org.springframework.security:spring-security-crypto:5.8.1'
이렇게 작성하면 암호화 라이브러리만 사용가능하다.
그리고 스프링부트 서버를 재시작하고 다시 테스트 해보니 성공! 🥳
개발자로서 꼭 한번쯤은 겪어봐야한다는 CORS에러의 매운맛을 제대로 본 하루였다.
이 에러를 해결하기 위해 여러 글을 참고 했는데 SOP, CORS 에 대해서는 좀 더 깊게 공부해봐야 될 것같다.
물론 지금은 시간이 없으니 나중에 꼬옥..!
'백엔드' 카테고리의 다른 글
Redis 사용하여 JWT RefreshToken 저장하기 (0) | 2025.04.03 |
---|---|
UPSERT의 동작원리와 기준 컬럼 설정 유의점 (0) | 2025.03.14 |
DB의 날짜와 프론트의 날짜 다른(1일 더 차이나는) 오류 해결 (0) | 2025.03.14 |
application-dev.yml 적용하는 방법 (0) | 2025.02.20 |
자바 컴파일 과정 (0) | 2025.01.14 |