Spring Security와 CORS: 크로스 도메인 요청 처리 기법 알아보기

2023. 10. 21. 00:28·Spring/Spring Security
반응형

웹 애플리케이션을 개발하면서 보안과 리소스 공유는 중요한 고려사항이다. 이번에는 Spring Security와 CORS(Cross-Origin Resource Sharing)에 대해서 자세히 알아보자

 

 

1. 크로스 도메인(Cross-Domain) 이해하기


1-1. 크로스 도메인

  • 정의: 크로스 도메인은 한 도메인에서 실행되는 웹 페이지가 다른 도메인의 리소스에 접근할 때 발생하는 상황을 의미한다.
  • 예시: http://domain-a.com에서 로딩된 웹 페이지가 http://domain-b.com의 이미지나 API에 접근하는 것이다.

cross domain

  • domain-a.com: 여기서 로딩된 웹 페이지가 다른 도메인의 리소스에 접근하려고 한다.
  • domain-b.com: 이 도메인은 이미지나 API와 같은 리소스를 제공한다.
  • Access: domain-a.com의 웹 페이지가 domain-b.com의 리소스에 접근한다.

 

1-2. 리소스 공유

  • 정의: 웹에서 "리소스"는 일반적으로 이미지, CSS, JavaScript 파일, API 엔드포인트 등을 의미한다.
  • 중요성: 웹 페이지가 원활하게 동작하기 위해서는 다양한 리소스를 로드해야 한다. 때로는 다른 도메인의 리소스 접근이 필요할 수 있다.
  • 리소스 공유의 예시는 위의 그림에서도 볼 수 있다. 여기서 "리소스"는 domain-b.com의 "Image/API"를 의미하며, domain-a.com의 웹 페이지가 이 리소스에 "Access"하고 있다.

 

1-3. CORS(Cross-Origin Resource Sharing) 기본 개념

  • 정의: CORS는 크로스 도메인 간의 리소스 공유를 가능하게 하는 웹 보안 메커니즘이다.
  • 동일 출처 정책(Same-Origin Policy): 웹 보안의 기본 원칙 중 하나는 동일 출처 정책이다. 이에 따라 웹 페이지는 동일한 출처에서만 리소스를 불러올 수 있다.
  • CORS 설정은 서버 측에서 이루어지며, 이를 통해 어떤 도메인에서의 접근을 허용할지를 브라우저에 알려준다.

1-4. CORS와 리소스 공유의 연계

  • 동작 원리: CORS 설정을 통해 서버는 브라우저에게 어떤 도메인에서의 접근을 허용할지 알려준다. 이를 통해 안전한 크로스 도메인 리소스 공유가 가능하다.
  • 예시: http://domain-b.com의 서버가 적절한 CORS 헤더를 설정하면, http://domain-a.com에서 http://domain-b.com/api/data의 리소스에 안전하게 접근할 수 있다.
  • 위의 그림을 보면 CORS 설정을 통해, domain-b.com의 서버는 domain-a.com의 웹 페이지에게 이 리소스에 안전하게 접근할 수 있음을 알려준다.

 

2. 어떤 프로젝트에서 이 CORS 관련 문제를 고려해야 할까?


Cors Project
Cors Project

  • Client (React, iOS App): 클라이언트 측에서는 웹 페이지가 실행되며, 서버에 리소스를 요청한다.
  • Server (Spring Boot): 서버 측에서는 API 엔드포인트를 제공하며, 클라이언트의 요청을 처리한다.
  • HTTP Request (Origin Header): 클라이언트가 서버에 요청을 보낼 때 "Origin" 헤더를 포함한다.
  • HTTP Response (CORS Headers): 서버는 응답에 CORS 관련 헤더를 포함하여 클라이언트에게 보낸다.

 

코드 예시

  • Spring Boot에서 CORS를 설정하는 방법은 다음과 같다. (Spring Boot 3.x.x 기준)
// Global CORS Configuration (WebConfig 설정)
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("http://localhost:3000", "http://another-domain.com")
                .allowedMethods("GET", "POST", "PUT", "DELETE")
                .allowCredentials(true);
    }
}

// Spring Security CORS Configuration (시큐리티 설정 -> Spring Security5.7이상은 Bean등록방식)
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
                .cors(cors -> cors.configurationSource(corsConfigurationSource()))
                .authorizeHttpRequests(auth -> auth
                      .requestMatchers("/resources/**", "/static/**").permitAll()
                      .anyRequest().authenticated()
                ).build();
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        // fixme: cors.setAllowedOrigins(Arrays.asList("<YOUR_DOMAIN>")); // 예: "https://your-ios-app.com"
        configuration.setAllowedOrigins(Arrays.asList("*"));
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));
        configuration.setAllowedHeaders(Arrays.asList("X-Requested-With", "Content-Type", "Authorization", "X-XSRF-token"));
        configuration.setAllowCredentials(false);
        configuration.setMaxAge(3600L);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
  • 이렇게 설정하면, 다양한 출처에서의 요청을 안전하게 처리할 수 있다.


 

3. CORS 동작 원리


브라우저는 리소스를 요청할 때 "Origin" 헤더를 포함하여 보낸다.

3-1. Origin 헤더란?

  • 클라이언트가 서버에 요청을 보낼 때 HTTP 헤더 중 하나로 "Origin" 헤더를 포함한다.
  • 이 헤더는 요청을 보내는 웹 페이지의 출처(도메인, 프로토콜, 포트)를 명시하여 서버가 이 정보를 통해 해당 요청이 허용된 출처에서 발생했는지 판단한다.
// HttpServletRequest를 통한 Origin 헤더 값 읽기
import javax.servlet.http.HttpServletRequest;

public void someControllerMethod(HttpServletRequest request) {
    String originHeader = request.getHeader("Origin");
    // originHeader 변수에는 "http://www.some-origin.com" 같은 값이 저장된다.
}
  1. 서버는 이 "Origin" 헤더를 확인하고, 허용된 출처에서의 요청인지 판단한다.
  2. 서버는 응답 헤더에 "Access-Control-Allow-Origin"을 포함하여 브라우저에 보낸다.
  3. 브라우저는 이 헤더를 검사하여 요청이 허용되면 리소스에 접근을 허용한다.

 

3-2. 주요 헤더 정보

  • Access-Control-Allow-Origin: 허용되는 출처를 지정한다.
  • Access-Control-Allow-Methods: 허용되는 HTTP 메서드를 지정한다.
  • Access-Control-Allow-Headers: 허용되는 HTTP 헤더를 지정한다.
  • Access-Control-Allow-Credentials: 쿠키나 인증 헤더를 허용할지 지정한다.
  • Access-Control-Expose-Headers: 이 헤더는 안전한 커스텀 헤더를 추가할 수 있도록 해준다.
  • Access-Control-Max-Age: 이 헤더는 preflight 요청의 결과를 캐시 하는 시간을 지정한다.


 

4. Simple Request와 Preflight Request에 대한 설명


  • 아래 그림은 Simple Request와 Preflight Request, 그리고 이들이 Spring Boot에서 어떻게 사용되는지를 나타낸다.

simple request & preflight request

  • Simple Request:
    • GET, POST, HEAD 메서드를 사용하며, Spring Boot에서는 주로 Read-Only 요청, 폼 전송, 기본 CRUD 작업에 사용된다.
  • Preflight Request:
    • OPTIONS 메서드를 사용하여 복잡한 요청을 미리 검증한다. Spring Boot에서는 커스텀 헤더 사용, 비-GET, POST 메서드, 복잡한 요청 등에 사용된다.
  • Spring Boot:
    • @CrossOrigin 어노테이션, WebMvcConfigurer 인터페이스 구현, HttpSecurity 객체를 통한 설정 등 다양한 방법으로 CORS를 설정할 수 있다.

 

4-1. 코드 예시

  • Simple Request 예시
// 간단한 GET 요청 처리
@RestController
@RequestMapping("/api/items")
public class ItemController {
    /**
     * 아이템 정보를 가져옵니다.
     * @param id 아이템 ID
     * @return 아이템 정보
     */
    @GetMapping("/{id}")
    public Item getItem(@PathVariable Long id) {
        return itemService.getItem(id);
    }
}

설명: 위의 예시는 GET 메서드를 사용하여 아이템 정보를 가져오는 간단한 요청을 처리한다. GET은 Simple Request의 조건 중 하나인 HTTP 메서드에 해당하므로 이 예시는 Simple Request에 해당한다.

  • Preflight Request 예시
// 커스텀 헤더와 DELETE 메서드를 사용한 예
@RestController
@RequestMapping("/api/files")
public class FileController {
    /**
     * 파일을 삭제합니다.
     * @param id 파일 ID
     */
    @DeleteMapping("/{id}")
    public void deleteFile(@PathVariable Long id) {
        fileService.deleteFile(id);
    }
}

설명: 위의 예시는 DELETE 메서드를 사용하여 파일을 삭제하는 요청을 처리한다. DELETE 메서드는 Simple Request의 조건에 해당하지 않는 HTTP 메서드이므로, 이 요청을 처리하기 전에 브라우저는 Preflight Request를 보내게 된다.


 

5. CORS 설정과 실습


cors설정과 실습
cors설정과 실습

5-1. Spring Boot와 Spring Security에서 CORS 설정: Spring에서 CORS 설정을 할 수 있는 여러 방법이 있다.

  • @CrossOrigin 어노테이션 사용: 컨트롤러나 핸들러 메서드 레벨에서 사용 가능하다.
  • WebMvcConfigurer를 구현: 세부적인 설정이 가능하다.
  • Spring Security에서의 CORS 설정: HttpSecurity 객체를 통해 설정할 수 있다.

5-2. CORS 실습: Spring Boot와 React 간의 통신

문제 상황:

  • Spring Boot 서버와 React 클라이언트가 다른 포트에서 실행되고 있을 때, 브라우저의 보안 정책 때문에 CORS(Cross-Origin Resource Sharing) 문제가 발생할 수 있다.

해결 전 상황

  • Spring Boot 서버: http://localhost:8080에서 실행되는 간단한 REST 컨트롤러가 있다.
@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello() {
        return "Hello, world!";
    }
}
  • React 클라이언트: http://localhost:3000에서 실행되며, Spring Boot 서버에 요청을 보낸다.
fetch("<http://localhost:8080/hello>")
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.log('CORS 문제 발생:', error));
  • 지금 이 상태에서는 브라우저 콘솔에서 CORS 관련 오류가 발생한다.

 

해결 방법:

1. @CrossOrigin 어노테이션 사용: 컨트롤러 레벨에서 CORS를 허용한다.

@RestController
@CrossOrigin(origins = "<http://localhost:3000>")  // 이 부분 추가
public class HelloController {
    @GetMapping("/hello")
    public String hello() {
        return "Hello, world!";
    }
}

2. 전역 CORS 설정: WebMvcConfigurer를 구현하여 전역에서 CORS를 설정한다.

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/hello")
                .allowedOrigins("<http://localhost:3000>");
    }
}
  • 이렇게 설정하면, React 앱에서 Spring Boot 서버로의 요청이 정상적으로 처리되며 CORS 문제가 해결된다.

 


2023.10.21 - [SpringBoot 개발/Spring Security] - Spring Security6 - Authentication(인증)

 

Spring Security6 - Authentication(인증)

코딩은 글쓰기라고 생각한다. 꾸준히 기록하며 내 개발 실력을 키울것이다.

curiousjinan.tistory.com

2023.08.07 - [SpringBoot 개발/Spring Security] - SpringBoot3.1 + SpringSecurity6 + Thymeleaf - form형태 jwt 로그인(1)

 

SpringBoot3.1 + SpringSecurity6 + Thymeleaf - form형태 jwt 로그인(1)

코딩은 글쓰기라고 생각한다. 꾸준히 기록하며 내 개발 실력을 키울것이다.

curiousjinan.tistory.com

 

반응형

'Spring > Spring Security' 카테고리의 다른 글

[Spring] 스프링 시큐리티 설정이 @Bean 기반 구성으로 바뀐 이유  (0) 2024.08.23
Spring Security6 - Authentication(인증)  (1) 2023.10.21
Spring Boot 3.1 & Spring Security 6: Security Config 최적화 리팩토링 (12편)  (0) 2023.09.04
Spring Boot 3.1 & Spring Security 6: JWT 검증 리팩토링 (11편)  (2) 2023.09.04
Spring Security 6 이해하기: 동작 원리와 보안 기능 탐구  (0) 2023.09.04
'Spring/Spring Security' 카테고리의 다른 글
  • [Spring] 스프링 시큐리티 설정이 @Bean 기반 구성으로 바뀐 이유
  • Spring Security6 - Authentication(인증)
  • Spring Boot 3.1 & Spring Security 6: Security Config 최적화 리팩토링 (12편)
  • Spring Boot 3.1 & Spring Security 6: JWT 검증 리팩토링 (11편)
Stark97
Stark97
dig04059@gmail.com 링크드인 소통도 환영합니다!
  • Stark97
    오늘도 개발중입니다
    Stark97
  • 전체
    오늘
    어제
    • 분류 전체보기 (256) N
      • 개발지식 (20)
        • 스레드(Thread) (8)
        • WEB, DB, GIT (3)
        • 디자인패턴 (8)
      • AI (7) N
      • JAVA (21)
      • Spring (88)
        • Spring 기초 지식 (35)
        • Spring 설정 (6)
        • JPA (7)
        • Spring Security (17)
        • Spring에서 Java 활용하기 (8)
        • 테스트 코드 (15)
      • 아키텍처 (6)
      • MSA (15)
      • DDD (12)
      • gRPC (9)
      • Apache Kafka (19)
      • DevOps (23)
        • nGrinder (4)
        • Docker (1)
        • k8s (1)
        • 테라폼(Terraform) (12)
      • AWS (32)
        • ECS, ECR (14)
        • EC2 (2)
        • CodePipeline, CICD (8)
        • SNS, SQS (5)
        • RDS (2)
      • notion&obsidian (3)
      • 채팅 서비스 (1)
      • 팀 Pulse (0)
  • 링크

    • notion기록
    • 깃허브
    • 링크드인
  • hELLO· Designed By정상우.v4.10.0
Stark97
Spring Security와 CORS: 크로스 도메인 요청 처리 기법 알아보기
상단으로

티스토리툴바