반응형
웹 애플리케이션을 개발하면서 보안과 리소스 공유는 중요한 고려사항이다. 이번에는 Spring Security와 CORS(Cross-Origin Resource Sharing)에 대해서 자세히 알아보자
1. 크로스 도메인(Cross-Domain) 이해하기
1-1. 크로스 도메인
- 정의: 크로스 도메인은 한 도메인에서 실행되는 웹 페이지가 다른 도메인의 리소스에 접근할 때 발생하는 상황을 의미한다.
- 예시: http://domain-a.com에서 로딩된 웹 페이지가 http://domain-b.com의 이미지나 API에 접근하는 것이다.
- 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 관련 문제를 고려해야 할까?
- 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" 같은 값이 저장된다.
}
- 서버는 이 "Origin" 헤더를 확인하고, 허용된 출처에서의 요청인지 판단한다.
- 서버는 응답 헤더에 "Access-Control-Allow-Origin"을 포함하여 브라우저에 보낸다.
- 브라우저는 이 헤더를 검사하여 요청이 허용되면 리소스에 접근을 허용한다.
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:
- 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 설정과 실습
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 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 |