Spring_14_CorsConfig

OngTK·2025년 10월 12일

Spring

목록 보기
14/25

📌 CORS 핵심 요점

  • Origin(출처) = 프로토콜 + 호스트 + 포트 조합이 다르면 교차 출처로 간주됨.
  • 브라우저는 보안을 위해 사전요청(Preflight, OPTIONS) 으로 서버에 허용 여부를 확인함.
  • 서버가 응답 헤더로 허용을 명시해야 실제 요청(POST/GET 등)이 진행됨.
  • 쿠키/세션을 함께 쓰려면 withCredentials: true + 서버의 allowCredentials(true) + 명시적 allowedOrigins 조합 필요.

1) 프런트: Axios 요청 패턴 (React)

withCredentials: true세션/쿠키 기반 인증 유지 가능.

// [1] 기본 GET (세션/쿠키 사용 안 함)
const r1 = await axios.get("http://localhost:8080/axios");
console.log(r1.data);

// [2] 로그인 (세션/쿠키 사용함)
const obj = { id: "admin", pwd: "qwer1234" };
const r2 = await axios.post("http://localhost:8080/axios", obj, { withCredentials: true });
console.log(r2.data);

// [3] 내 정보 조회 (세션/쿠키 유지)
const r3 = await axios.get("http://localhost:8080/axios/info", { withCredentials: true });
console.log(r3.data);

(a) x-www-form-urlencoded 전송

// 폼 DOM을 전송하려면 URLSearchParams로 인코딩
const form = formRef.current;
const params = new URLSearchParams(new FormData(form));
const r = await axios.post("http://localhost:8080/axios/form", params, {
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  withCredentials: true, // 쿠키 필요 시
});
console.log(r.data);

(b) multipart/form-data 전송 (파일 업로드)

const form = formRef.current;
const formData = new FormData(form);

const r = await axios.post("http://localhost:8080/axios/formdata", formData, {
  headers: { "Content-Type": "multipart/form-data" },
  withCredentials: true,
});
console.log(r.data);

TIP: withCredentials 를 쓰면 서버 CORS에서 allowCredentials(true) 필수.
또한 allowedOrigins("*")동시에 allowCredentials(true) 를 사용할 수 없음 → 명시적 Origin 지정 필요.


2) 백엔드: Spring Boot CORS 설정

(A) 단순(Web MVC) CORS 설정

WebMvcConfigurer 사용. 보통 보안 없이 간단한 API만 있을 때.

// CorsConfig.java
@Configuration
public class CorsConfig implements WebMvcConfigurer {

  @Override
  public void addCorsMappings(CorsRegistry registry) {
    registry.addMapping("/**")
        .allowedOrigins("http://localhost:3000", "http://localhost:5173")   // React dev 서버
        .allowedMethods("GET","POST","PUT","PATCH","DELETE","OPTIONS")		// 허용HTTP METHOD
        .allowedHeaders("*")
        .exposedHeaders("Location") // 필요시 노출 헤더
        .allowCredentials(true)     // 쿠키 허용
        .maxAge(3600);
  }
}

(B) Spring Security와 함께 사용할 때

SecurityFilterChain에서 http.cors() 활성화 + CorsConfigurationSource 빈 제공.

// SecurityConfig.java
@Configuration
@EnableWebSecurity
public class SecurityConfig {

  @Bean
  public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
      .cors(cors -> cors.configurationSource(corsConfigurationSource()))
      .csrf(csrf -> csrf.disable()); // 세션/쿠키 기반이면 CSRF 전략 별도 고려
    return http.build();
  }

  @Bean
  public CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration cfg = new CorsConfiguration();
    cfg.setAllowedOrigins(List.of("http://localhost:3000","http://localhost:5173"));
    cfg.setAllowedMethods(List.of("GET","POST","PUT","PATCH","DELETE","OPTIONS"));
    cfg.setAllowedHeaders(List.of("*"));
    cfg.setAllowCredentials(true);
    cfg.setExposedHeaders(List.of("Location"));
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", cfg);
    return source;
  }
}

TIP: Spring Security를 사용한다면 WebMvcConfigurer만으로는 부족할 수 있음. 반드시 http.cors()CorsConfigurationSource 로 일치되는 정책을 등록.

(C) allowedOriginPatterns (와일드카드) 사용

운영에서 여러 서브도메인을 허용해야 하는 경우.

cfg.setAllowedOriginPatterns(List.of("https://*.example.com"));
cfg.setAllowCredentials(true); // 패턴 사용 시에도 크리덴셜은 명시적 허용 필요

3) 쿠키/세션 인증과 CORS

  • 프런트: withCredentials: true
  • 백엔드: allowCredentials(true) + 명시적 출처 지정
  • Set-Cookie: 크로스 사이트 전송 필요 시 SameSite=None; Secure 설정 필요(HTTPS 권장)

예) Spring에서 응답 쿠키 생성 시

ResponseCookie cookie = ResponseCookie.from("SESSIONID", sessionId)
    .httpOnly(true)
    .secure(true)           // HTTPS에서만
    .sameSite("None")       // 크로스 사이트 전송 허용
    .path("/")
    .build();
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());

4) Controller 예시 (세션 유지 플로우)

@RestController
@RequestMapping("/axios")
public class AxiosController {

  @PostMapping
  public ResponseEntity<String> login(@RequestBody Map<String,String> body, HttpSession session) {
    // 검증 로직...
    session.setAttribute("login", body.get("id"));
    return ResponseEntity.ok("ok");
  }

  @GetMapping("/info")
  public ResponseEntity<?> myInfo(HttpSession session) {
    Object login = session.getAttribute("login");
    if (login == null) return ResponseEntity.status(401).body("unauthorized");
    return ResponseEntity.ok(Map.of("user", login));
  }
}

5) 로컬 개발 환경 대안: 프록시 사용

프런트 개발 서버에서 프록시 설정을 두면 브라우저는 같은 Origin으로 인식 → CORS 회피.

Vite 예시 (vite.config.ts)

export default defineConfig({
  server: {
    proxy: {
      "/api": {
        target: "http://localhost:8080",
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ""),
      },
    },
  },
});

프런트 요청은 /api/... 로, 서버는 /... 로 받도록 매핑.


6) 흔한 오류와 해결

  • 오류: CORS policy: No 'Access-Control-Allow-Origin' header
    → 서버 CORS 미설정 또는 Origin 불일치. allowedOrigins 확인.

  • 오류: 쿠키가 안 붙음
    → 프런트 withCredentials:true + 서버 allowCredentials(true) + 명시적 Origin 필요. SameSite=None; Secure도 확인.

  • 오류: Preflight 403/404
    → 서버가 OPTIONS 허용하지 않음. allowedMethods/필터 체인 확인.

  • 오류: application/x-www-form-urlencoded 전송 실패
    URLSearchParams(new FormData(form)) 로 인코딩, 헤더 정확히 지정.


7) 전체 플로우 요약

  1. React(Axios) 요청 시 withCredentials 및 알맞은 Content-Type 설정.
  2. Spring CORS 정책에서 Origin/Methods/Headers/Credentials 일치.
  3. 세션/쿠키 기반이면 SameSite=None; Secure 고려.
  4. 개발 초기에는 CORS 정확히 맞추기 어렵다면 프록시 활용.

8) 참고: 제공된 JSX와 매핑되는 포인트

  • GET/POST 기본 요청, withCredentials 사용 → CORS의 allowCredentials(true) 및 명시적 allowedOrigins 필요.
  • 폼 전송은 URL 인코딩, 파일 업로드는 FormData + multipart/form-data.
  • 헤더 오타 교정: application/x-www-form-urlencoded (정확).
profile
2025.05.~K디지털_풀스택 수업 수강중

0개의 댓글