[Spring Security] CORS

신명철·2022년 5월 14일
8

Spring Security

목록 보기
5/6

들어가며

프로젝트 중 react와 통신해야할 상황이 생겼다. cors에러를 예상하고 있었지만 막상 닥치니 해결하기까지 너무 오랜 시간이 걸렸고, 많은 우여곡절이 있었다.


CORS(Cross-Origin Resource Sharing)

CORS는 영어 그대로 교차 출처를 공유할 수 있는 권한을 부여하도록 브라우저에 알려주는 정책이다. 서로 다른 출처를 가진 Application이 서로의 Resource 에 접근할 수 있도록 해준다.

Origin?

기본적으로 프로토콜, 호스트, 포트 를 통틀어서 Origin(출처) 라고 한다.

즉 서로 같은 출처란 이 셋이 동일한 출처를 말하고, 여기서 하나라도 다르다면 Cross Origin, 즉 교차출처가 되는 것이다.

  • http://localhost:8080 : Spring Boot
  • http://localhost:3000 : React

보안상의 이유로, 브라우저는 스크립트에서 시작한 Cross Origin HTTP Request를 제한한다. 즉, SOP(Same Origin Policy)를 따른다.

React와 Spring Boot의 port 가 서로 다르기 때문에 cors 정책 위반 에러가 나왔던 것이다.


CORS 시나리오

Request 에는 Origin 헤더가 있고, Response에는 Access-Control-Allow-Origin 헤더가 있는데 이 두 가지가 서로 같을 경우 같은 출처라고 인식한다는 것을 알고 있어야 한다.

1. Simple Request (단순 요청)

  • Simple RequestPreflight Request 없이 요청을 보내 서버는 이에 대한 응답으로 Access-Control-Allow-Origin 헤더를 응답하는 방식이다.
  • Credential이 없는 요청의 경우 "*" 와일드 카드를 통해 브라우저의 Origin에 상관없이 모든 리소스에 접근하도록 허용한다.
    -> Access-Control-Allow-Origin : Origin or *

2. Preflight Request

  • 가장 일반적으로 마주치는 시나리오이다.

  • 먼저, OPTIONS 메서드를 통해서 다른 도메인의 리소스로 HTTP Request를 보내고 실제 요청이 전송하기에 안전한지를 확인한다. Cross-Site Request는 유저 데이터에 영향을 줄 수 있기 때문에 이와 같이 미리 전송(preflighted)한다.

  • Access-Control-Request-Method:<method>

    • Access-Control-Request-Method 헤더는 실제 요청에 어떤 HTTP Method 가 사용될지 서버에게 알려주기 위해서 preflight request에 사용된다.
  • Access-Control-Allow-Mehtods:<method> or [<method>]*

    • Access-Control-Request-Method 헤더는 리소스에 접근할 때 허용되는 메서드를 지정한다. 이 헤더는 preflight request에 대한 응답으로 사용된다.
  • Access-Control-Request-Headers: <header-name>[, <header-name>]*

    • Access-Control-Request-Headers 헤더는 실제 요청에서 어떤 HTTP Header를 사용할지 서버에 알려주기 위해서 preflight request에 사용된다
  • `Access-Control-Allow-Headers: [, ]*

    • Access-Control-Allow-Headers 헤더는 preflight request에 대한 응답으로 실제 요청 시 사용할 수 있는 HTTP Header를 알려준다.

3. Credentials Request

  • HTTP CookieHTTP Authentication 정보를 사용한다.
  • Credentials이 필요한 CORS에는 FrontResponse HeaderwithCredentials=true, BackendResponse HeaderAccess-Control-Allow-Origin을 포함해야 한다.
  • Credential Request는 Cross-site XMLHttpRequest나 Fetch 호출은 기본적으로 자격 증명을 보내지 않기 때문에 credentials 옵션을 통해 인증 보안을 강화한다.
  • 이 요청에 인증과 관련된 정보를 담을 수 있게 해주는 옵션이 바로 credentials 옵션이다.
  • Access-Control-Allow-Credentials: true로 응답하지 않으면, 사용자 인증이 필요한 리소스에 대한 응답은 무시되고 웹 컨텐츠는 제공되지 않는다.
  • Access-Control-Allow-Credentials: true | false

    • Request with Credential 방식을 사용할 것인지를 지정한다.
    • Access-Control-Allow-Credentials 헤더는 credentials 플래그가 true일 때 요청에 대한 응답을 표시할 수 있는지를 나타낸다.

The HTTP Access-Control-Allow-Credentials is a Response header. The Access-Control-Allow-Credentials header is used to tell the browsers to expose the response to front-end JavaScript code when the request’s credentials mode Request.credentials is “include” | https://www.geeksforgeeks.org/http-headers-access-control-allow-credentials/


문제 해결

Spring Security

CORS 반드시 Spring Security 에 앞서 처리되어야 한다. preflight requestJSESSIONID같은 쿠키를 포함하고 있지 않고, 이는 Request가 인증되지(not authenticated)않은 사용자라고 판단하고 거절해버리기 때문이다.

CorsFilter를 이용해서 CORS를 해결할 수 있었다. CorsFilter 는 CORS를 다룰 수 있는 가장 쉬운 방법이다. CorsConfigurationSource를 제공해 CorsFilterSpring Security에 통합할 수 있다.

@Bean
	CorsConfigurationSource corsConfigurationSource() {
		CorsConfiguration configuration = new CorsConfiguration();

		configuration.setAllowedOriginPatterns(Arrays.asList("*"));
		configuration.setAllowedMethods(Arrays.asList("HEAD","POST","GET","DELETE","PUT"));
		configuration.setAllowedHeaders(Arrays.asList("*"));
		configuration.setAllowCredentials(true);
		
		UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
		source.registerCorsConfiguration("/**", configuration);
		return source;
	}

만약 Spring MVCCORS 지원을 사용한다면 Spring SecurityCorsConfigurationSource 설정을 생략할 수 있다고 한다. Spring SecuritySpring MVC에서 제공되는 CORS 설정을 활용할 수 있기 대문이다.

Spring MVC

Spring MVC에서 CORS를 적용하는 방법은 크게 2가지로 나뉜다.

  1. 컨트롤러에서 @CrossOrigin 어노테이션을 사용해서 설정하는 방법
  2. 전역적으로 Spring MVC 설정에서 CORS를 설정하는 방법

어노테이션 활용 방법

@RestController
@RequestMapping("/account")
public class AccountController {
 
    @CrossOrigin
    @RequestMapping(method = RequestMethod.GET, path = "/{id}")
    public Account retrieve(@PathVariable Long id) {
        // ...
    }
 
    @RequestMapping(method = RequestMethod.DELETE, path = "/{id}")
    public void remove(@PathVariable Long id) {
        // ...
    }
}
  • 모든 Origin이 허용
  • @RequestMapping 에 명시된 HTTP Method(GET)이 허용된다.
@CrossOrigin(origins = "http://example.com", maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {
 
    @RequestMapping(method = RequestMethod.GET, path = "/{id}")
    public Account retrieve(@PathVariable Long id) {
        // ...
    }
 
    @RequestMapping(method = RequestMethod.DELETE, path = "/{id}")
    public void remove(@PathVariable Long id) {
        // ...
    }
}
  • 클래스 레벨에서 어노테이션을 사용하면 해당 컨트롤러에 속해있는 핸들러 메서드 모두에게 CORS 정책이 적용된다.
  • @CrossOrigin 애노테이션을 통해 origins, methods, allowedHeaders, exposedHeaders, allowCredentials, maxAge 모두 설정할 수 있다.

전역적으로 CORS 설정하는 방법

필터 기반 솔루션을 사용하는 것과 유사하지만 Spring MVC 내에서 선언하고 @CrossOrigin 설정과 결합할 수 있다.

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**");
                .allowedOrigins("http://www.example.com")
                .allowedMethods("*")
                .allowCredentials(false)
                .maxAge(3000);
    }
}
  • 위의 JavaConfig를 통해 paths, origins, methods, allowedHeaders, exposedHeaders, allowCredentials, maxAge 모두 설정할 수 있다.

<출처>
profile
내 머릿속 지우개

0개의 댓글