이 파일의 학습 목적
03b — CORS · ResponseEntity · axios interceptor · @PathVariable
React에서 Spring Boot API를 처음 호출하면 CORS policy 에러가 뜬다.
JWT 토큰을 매번 헤더에 수동으로 붙이면 코드가 엄청 지저분해진다. 03b는 이 실전 문제들을 해결하는 4가지 — CORS 설정·ResponseEntity·axios interceptor·@PathVariable을 정리한다. 웹/통신 시리즈의 마지막이자 04 프로젝트 코드로 넘어가기 직전 파일이다.
이 파일에서 배울 4가지
- ① CORS — 왜 에러 나는지, @CrossOrigin·CorsConfig 차이
- ② ResponseEntity — 상태코드·헤더·바디를 직접 제어
- ③ axios interceptor — 모든 요청에 JWT 자동 첨부
- ④ @PathVariable · @RequestParam — URL 변수 처리
프로젝트 코드 연결
- · 2차
CorsConfig.java → 3차 SecurityConfig cors()
- · 2·3차 axios interceptor — localStorage JWT 자동 첨부
- ·
@PathVariable Long id — 게시글 수정·삭제·상세
시리즈 순서:
03a 동기·비동기·Ajax →
03b ← 지금 여기
→ 04a 1차 프로젝트 코드 해설
→ 04b 2차 → 04c 3차
01 — CORS란? 왜 필요한가?
React(3000)와 Spring Boot(8080) — 포트가 달라서 생기는 브라우저 보안 정책
🚧
Cross-Origin Resource Sharing
Browser Security
🏢 비유 — 다른 건물 출입 통제
React 앱 = A 건물 (포트 3000)
Spring Boot = B 건물 (포트 8080)
브라우저 = 경비원 → "A 건물 직원이 B 건물에 무단 출입하면 막겠다!"
→ Spring Boot에서 "React는 허용됨"이라고 명시해줘야 통과
React
Frontend
localhost:3000
→ ✗
→ ✓
허용 설정
Spring Boot
localhost:8080
❌ CORS 설정 없음 → 오류
Access to XMLHttpRequest at
'http://localhost:8080/api/...'
from origin
'http://localhost:3000'
has been blocked by CORS policy
✅ CORS 설정 후 → 통과
Access-Control-Allow-Origin:
http://localhost:3000
Java — 2차 프로젝트 CORS 설정 — WebConfig.java
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:3000")
.allowedMethods("GET","POST","PUT","DELETE")
.allowedHeaders("*")
.allowCredentials(true);
}
}
Java — 3차 프로젝트 CORS 설정 — SecurityConfig 내부
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("http://localhost:3000"));
config.setAllowedMethods(List.of("GET","POST","PUT","DELETE"));
config.setAllowedHeaders(List.of("*"));
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source =
new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}
⚠️ Docker(4차) 환경에서 CORS 변경
Docker에서는 nginx가 React와 Spring Boot를 같은 포트(80)로 묶어줌
→ 포트가 달라서 생기던 CORS 문제 자체가 사라짐
→ baseURL: '' (빈 문자열) — nginx가 /api/ 요청을 Spring Boot로 프록시
02 — ResponseEntity
HTTP 응답을 직접 제어 — 상태코드 + 헤더 + 바디 한 번에
📬
ResponseEntity — 응답 봉투
HTTP Response
📦 비유 — 택배 상자
단순히 물건(데이터)만 보내는 게 아니라
상태코드(배송 성공/실패) + 헤더(운송장) + 바디(물건) 를 한 번에 제어
→ 성공이면 200 + 데이터 / 실패면 400 + 에러 메시지
400 Bad Request
요청 데이터 오류
401 Unauthorized
인증 필요 · 토큰 없음
403 Forbidden
권한 없음 (로그인은 됨)
500 Server Error
서버 내부 오류
Java — ResponseEntity 실제 사용 패턴
return ResponseEntity.ok(boardList);
return ResponseEntity.status(HttpStatus.CREATED).body("등록됐어요");
return ResponseEntity.badRequest().body("이미 존재하는 아이디입니다.");
@PostMapping("/api/user/register")
public ResponseEntity<String> register(@RequestBody UserDto dto) {
try {
userService.register(dto);
return ResponseEntity.ok("회원가입 성공");
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest().body(e.getMessage());
}
}
💡 React에서 응답 상태코드 활용
axios는 2xx 상태코드면 .then(), 그 외엔 .catch()로 넘어감
→ Spring에서 상태코드를 명확히 내려줘야 React에서 성공/실패 분기가 쉬워짐
03 — axios interceptor
모든 요청·응답을 가로채서 공통 처리 — JWT 토큰 자동 첨부의 핵심
🔀
요청/응답 중간 가로채기
Middleware
🛂 비유 — 공항 출입국 심사
요청 interceptor = 출국 심사 — 모든 여권(토큰)을 자동 확인·첨부하고 보냄
응답 interceptor = 입국 심사 — 모든 응답을 체크, 401이면 로그인 페이지로 강제 이동
→ 각 컴포넌트마다 토큰 붙이는 코드 작성 안 해도 됨
요청
axios.get('/api/board/list') 호출
→ 요청 interceptor 실행 → localStorage에서 token 꺼냄
→ Authorization: Bearer eyJhb... 헤더에 자동 첨부
→ Spring Boot로 전송
응답
Spring Boot 응답 수신
→ 응답 interceptor 실행 → 상태코드 확인
→ 200이면 그대로 반환 / 401이면 token 삭제 + 로그인 페이지 이동
JavaScript — 우리 프로젝트 api.js — interceptor 전체 코드
import axios from 'axios';
const api = axios.create({
baseURL: 'http://localhost:8080',
});
api.interceptors.request.use((config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
api.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
localStorage.removeItem('token');
window.location.href = '/login';
}
return Promise.reject(error);
}
);
export default api;
api.get('/api/board/list');
api.post('/api/board/write', data);
📌 interceptor 덕분에 각 컴포넌트에서 할 일이 없어짐
기존: 모든 axios 요청마다 headers: {Authorization: ...} 직접 작성
interceptor 후: api.get('/api/...')만 쓰면 토큰 자동 첨부
04 — @PathVariable · @RequestParam
URL에서 값을 꺼내는 두 가지 방법 — 경로 변수 vs 쿼리 파라미터
🔍
URL에서 값 꺼내기
URL Params
@PathVariable — 경로에 값 포함
@GetMapping("/api/board/detail/{id}")
public ResponseEntity<?> detail(
@PathVariable Long id) {
}
api.get(`/api/board/detail/${boardId}`)
@RequestParam — 쿼리스트링으로 전달
@GetMapping("/api/board/list")
public ResponseEntity<?> list(
@RequestParam(defaultValue="0") int page) {
}
api.get('/api/board/list', {params:{page:2}})
| 구분 | @PathVariable | @RequestParam |
| URL 형태 | /board/detail/5 | /board/list?page=2 |
| 사용 상황 | 특정 리소스 식별 (id) | 옵션·필터·페이징 |
| 기본값 설정 | 불가 (경로에 반드시 있어야 함) | defaultValue 설정 가능 |
| 프로젝트 예시 | /api/board/detail/{id} | /api/board/list?page=0 |
Java + JavaScript — 우리 프로젝트 실제 사용 패턴
@GetMapping("/api/board/detail/{id}")
public ResponseEntity<?> detail(@PathVariable Long id) {
return ResponseEntity.ok(boardService.getDetail(id));
}
@GetMapping("/api/board/list")
public ResponseEntity<?> list(
@RequestParam(defaultValue = "0") int page) {
return ResponseEntity.ok(boardService.getList(page));
}
api.get(`/api/board/detail/${id}`);
api.get('/api/board/list', {params:{page:currentPage}});
api.get(`/api/board/list?page=${currentPage}`);
웹/통신 기초 시리즈 — 전체 정리
03a + 03b — React ↔ Spring Boot 통신의 전체 그림
흐름 — React에서 게시글 목록 요청 → Spring Boot → 응답 전 과정
const res = await api.get('/api/board/list', {params:{page:0}});
setBoards(res.data);