프로젝트 환경 설명
spring boot(html, css, javascript, thymeleaf) 프론트
spring boot 백엔드
나는 백엔드 개발자여서 vue, react 같은 프론트 프레임워크를 전혀 모르지만 단순 SSR을 하는 프로젝트 보다는 이렇게라도 CSR을 적용하여 프론트 / 백엔드 통신 과정에 대한 이해와 여러가지 로컬/운영 환경에서의 이슈를 직접 경험하고 싶어 이렇게 구성했다.
로컬 환경에서 프론트 백엔드 통신 테스트 하기 (CORS 문제 해결하기)
프론트 설명 -> CORS -> 백엔드 설명
프론트 설명
front: application.yaml
spring:
profiles:
active: local
---
spring:
config:
activate:
on-profile: local
backend:
url: http://localhost:8081
server:
port: 8080
---
spring:
config:
activate:
on-profile: prod
backend:
url: http://localhost:8081
로컬 환경과 운영 환경의 백엔드 경로는 달라지기 때문에 프로필에 따라 이렇게 구분했다.
아직 운영 환경 구성이 되지 않은 상태이다.
GlobalModelAttribue
@ControllerAdvice
public class GlobalModelAttribute {
@Value("${spring.backend.url}")
private String backendUrl;
@ModelAttribute
public void addAttributes(Model model) {
model.addAttribute("backendUrl", backendUrl);
}
}
GlobalModelAttribue는 스프링 MVC에서 모든 컨트롤러가 공통으로 사용할 수 있는 모델 데이터를 추가하기 위해 사용된다.
@ControllerAdvice를 통해 전역적인 컨트롤러 제어를 하고 @ModelAttribute를 통해 컨트롤러 메서드에서 모델에 데이터를 추가하였다. 프로필에 따라 다르게 주입되는 백엔드 경로를 모델에 담아준다.
Home.html
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{layout/common::common-head(title='홈페이지')}"></head>
<div th:replace="~{layout/header::header}"></div>
<body>
<button id="testApiButton">API 테스트</button>
<div th:replace="~{layout/common::common-scripts}"></div>
<div th:replace="~{layout/footer::footer}"></div>
<span id="backend-url" th:attr="data-url=${backendUrl}"></span>
</body>
<script>
var backendUrl = document.getElementById("backend-url").dataset.url;
document.getElementById("testApiButton").addEventListener("click", function() {
fetch(backendUrl + "/api/test")
.then(response => response.text())
.then(data => {
console.log("API 응답:", data);
})
.catch(error => {
console.error("API 요청 실패:", error);
});
});
</script>
</html>
해당 뷰는 HomeController에서 랜더링되는 뷰이다.
간단하게 버튼을 누르면 백엔드 /api/test 경로로 text(String) 데이터를 받아올 수 있다.
CORS?
출처(Origin)이란?
- 프로토콜 (Protocol): 요청하는 URL의 프로토콜 (예: http, https)
- 호스트 (Host): 요청하는 URL의 도메인 (예: localhost, example.com)
- 포트 (Port): 요청하는 URL의 포트 (예: 8080, 443)
프로토콜 + 호스트 + 포트 = 출처(Origin)
즉, 같은 출처는 프로토콜 호스트 포트가 모두 같아야한다.
SOP (Same-Origin Policy)란?
SOP (Same-Origin Policy)는 브라우저 보안 모델으로, 같은 출처에서만 리소스를 서로 공유할 수 있도록 제한하는 정책이다.
즉, 한 웹 페이지에서 다른 출처의 리소스에 접근하는 것을 기본적으로 차단하는 방식이다.
SOP의 목적은 다른 출처에서 온 악성 코드가 브라우저에서의 중요한 데이터나 사용자 정보를 도용하는 것을 방지하기 위함이다.
CORS (Cross-Origin Resource Sharing)란?
CORS는 교차 출처 리소스 공유라는 의미로, 다른 출처(origin)에서 리소스를 공유하는 것을 허용하는 메커니즘이다.
SOP에 의해 기본적으로 차단되는 다른 출처의 요청을 서버에서 명시적으로 허용하는 방법을 제공한다.
CORS는 서버에서 헤더를 통해, 어떤 출처에서 오는 요청을 허용할지 지정한다.
CORS 종류
Simple Request
Simple Request는 예비 요청(Preflight) 과정 없이 자동으로 CORS 가 작동하여 서버에 본 요청을 한 후, 서버가 응답 헤더에 Access-Control-Allow-Origin 과 같은 값을 전송하면 브라우저는 서로 비교 후 CORS 정책 위반여부를 검사하는 방식이다 .
Simple Request 제약 사항
- GET, POST, HEAD 중의 한가지 Method를 사용해야 한다
- Custom Header 허용 X
- ContentType: application/x-www-form-urlencoded, multipart/form-data, text/plain
Preflight Request (예비요청)
브라우저는 요청을 한번에 보내지 않고, 예비 요청과 본 요청으로 나누어 서버에 전달하는데 브라우저가 예비요청을 보내는 것을 Preflight 라고 하며 이 예비요청의 메소드에는 OPTIONS가 사용된다.
예비 요청의 역할은 본 요청을 보내기 전에 브라우저 스스로 안전한 요청인지 확인하는 것으로 요청 사양이 Simple Request 에 해당하지 않을 경우 브라우저가 Preflight Request을 실행한다.
백엔드
back: application.yml
spring:
profiles:
active: local
---
spring:
config:
activate:
on-profile: local
front:
url: http://localhost:8080
server:
port: 8081
---
spring:
config:
activate:
on-profile: prod
front:
url: http://localhost:8080
TestController
@RestController
public class TestController {
@GetMapping("/api/test")
public String test() {
return "success";
}
}
단순하게 success 문자열을 응답하는 테스트 컨트롤러
WebConfig
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Value("${spring.front.url}")
private String frontUrl;
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins(frontUrl)
.allowedMethods("*")
.allowedHeaders("*")
.exposedHeaders("Authorization")
.allowCredentials(true)
.maxAge(3000);
}
}
WebMvcConfigurer를 구현하면 스프링 MVC 설정을 커스터마이즈할 수 있다.
@Value("${spring.front.url}") 프론트엔드 URL 주입
addCorsMappings() 메서드
- CORS(Cross-Origin Resource Sharing) 정책을 설정하는 메서드
- 프론트엔드와 백엔드의 출처(origin)가 다를 때 발생하는 CORS 문제를 해결할 수 있다.
- 프론트(출처)에서 오는 모든 HTTP 메서드, 모든 헤더 허용
- exposedHeaders("Authorization"): 클라이언트 측에서 Authorization 헤더를 포함한 응답을 읽을 수 있도록 노출
- allowCredentials(true): 클라이언트에서 자격 증명(쿠키나 인증 정보)을 서버에 보낼 수 있도록 허용(인증에 사용)
- maxAge(3000): Preflight 요청에 대한 캐시 시간을 설정했다. 3000초(50분) 동안 동일한 요청에 대해 Preflight 요청을 다시 보내지 않아도 된다.
테스트 성공

CORS 이슈가 발생하지 않고 프론트와 백엔드가 잘 통신한다.
다음 목표
이제 로컬 환경에서 프론트 백엔드 통신에 성공했으니 다음엔 실제 배포 환경에서 통신을 테스트해 보겠다.
또한 이왕 배포 환경을 구축하는 겸 Github Actions를 사용한 CI/CD 구축까지 해보겠다.
'사이드 프로젝트 기록' 카테고리의 다른 글
[EasyMarket] 스프링 이메일 인증 구현 (Redis/OTP) (0) | 2025.03.27 |
---|---|
[EasyMarket] Github Actions 사용으로 CI/CD 구축하기 (EC2 생성) (0) | 2025.03.26 |
[EasyMarket] 트랜잭션, 꼭 서비스에서 시작해야 할까? (0) | 2025.02.26 |
[EasyMarket] 세션 VS JWT 토큰 방식 (0) | 2025.02.07 |