Web/Spring

[Spring] CORS 관련 405에러 해결방법

실제 EC2에 서버를 배포하고 프론트의 브라우저에서 요청했을때 CORS 헤더응답을 받지 못하여 405 에러가 발생합니다.

 

 

그런데, 프론트의 브라우저에서만 이 에러가 발생합니다.

 

포스트맨으로 요청했을 때는 모든 메서드가 정상적으로 작동합니다.

이번 포스팅은 배포시에 왜 이런문제가 발생하고 어떻게 해결해야할 지 알아봅니다.

 

 

OPTIONS 메서드 요청

OPTIONS 메서드

  • OPTIONS 요청은 브라우저가 서버에게 지원하는 옵션들을 미리 요청하고 허가된 요청에 한해서 전송하기 위한 보안상의 목적이 있다.

네트워크 요청 시 실제 원하는 요청(GET, PUT, POST, DELETE) 전에 OPTIONS 요청을 보내는 경우가 존재합니다.

 

브라우저는 OPTIONS를 preflight하여 서버에서 허용하는 옵션을 미리 확인하고, 허용되지 않은 요청의 경우 405(Method Not Allowed)에러를 발생시키고 실제 요청은 전송하지 않습니다.

 

하지만 OPTIONS 메서드에는 토큰이 포함되어 있지 않기때문에 인터셉터에서 헤더의 토큰을 검증할때 걸러지게 됩니다.

아래 코드를 보면 token을 검증하고 실패하는 경우 예외를 반환합니다.

 

 

@Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String token = AuthorizationExtractor.extract(request);
        if (!jwtTokenProvider.validateToken(token) || token == null) {
            throw new AuthorizationException();
        }

        return setUsernameAndReturn(request, token);
    }

 

정리

  1. 브라우저에서 method를 OPTIONS로 요청 전송
  2. OPTIONS를 받은 서버가 응답헤더에 허용하는 옵션(Access-Control-Allow-*)을 포함해 전송
  3. 브라우저는 응답헤더의 옵션을 확인해 허용되지 않은 요청은 405에러 발생, 허용하는 경우 요청 전송

 

포스트맨에서는?

포스트맨에서는 이 OPTIONS 요청을 보내지 않고 바로 요청을 보내기 때문에 정상적으로 동작합니다.

 

 

CORS(Cross-Origin Resource Sharing)

추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에게 알려주는 체제입니다.

 

API를 사용하는 웹 애플리케이션은 자신의 출처와 동일한 리소스만 불러올 수 있으며, 다른 출처의 리소스를 불러오려면 그 출처에서 올바른 CORS 헤더를 포함한 응답을 반환해야 합니다.

 

응답에 CORS를 적용하는 방법은 여러가지가 있습니다.

  1. @CrossOrigin 추가하기
  2. WebConfig에 CORS 설정하기
  3. Proxy 만들기 (클라이언트에서)

 

@CrossOrigin

@CrossOrigin은 특정 도메인에 CORS를 적용할 수 있도록 스프링에서 제공하는 어노테이션입니다.

@CrossOrigin(origins = "<http://localhost:8080>") // 추가
@RestController
public class GreetingController {

  @GetMapping("/hello")
  public String hello() {
    return "안녕하세요?";
  }
}

하지만 프론트의 브라우저의 IP주소는 매번 바뀔 가능성이 있기 때문에 이번 미션에서는 적용할 수 없습니다.

만약 프론트 서버가 배포되어 고정된 특정 도메인(IP)에서 요청을 보낸다면 @CrossOrigin 어노테이션을 사용할 수 있겠죠.

(모든 도메인 요청을 여는 방법도 존재합니다.)

 

 

WebConfig에 CORS 설정하기

@CrossOrigin 에서는 사용할 컨트롤러 클래스레벨에 어노테이션을 붙여주어야 합니다.

WebConfig에서는 @CrossOrigin과 달리 모든 요청에 대한 전반적인 설정이 가능합니다.

@Configuration
public class WebConfig implements WebMvcConfigurer {
    public static final String ALLOWED_METHOD_NAMES = "GET,HEAD,POST,PUT,DELETE,TRACE,OPTIONS,PATCH";

    @Override
    public void addCorsMappings(final CorsRegistry registry) {
        registry.addMapping("/api/**")
            .allowedMethods(ALLOWED_METHOD_NAMES.split(","))
            .exposedHeaders(HttpHeaders.LOCATION);
    }
}

 

 

그래서 방법은?

  1. WebConfig에 응답으로 CORS 권한을 주도록 설정
  2. 인터셉터를 사용한다면 토큰검증시 OPTIONS 메서드요청이라면 항상 허용하도록 설정

 

만약 프론트 서버가 배포되어있다면 해당 리소스의 요청에 CORS 권한을 부여해놓으면 됩니다.

하지만 현재 장바구니 미션에서 프론트의 localhost의 브라우저에서 요청하는 경우 ip주소가 고정되어 있지 않을 가능성이 높습니다.

 

그래서 백엔드에서는 인터셉터에서 OPTIONS 요청을 무조건 허용하도록 하면 됩니다.

코드를 보면 request의 메서드를 받아 OPTIONS인지 확인하고 정상적인 반환을 하도록 처리합니다.

 

설정하기

 

1. @CrossOrigin 설정 또는 WebConfig설정을 해줍니다.

@Configuration
public class WebConfig implements WebMvcConfigurer {
    public static final String ALLOWED_METHOD_NAMES = "GET,HEAD,POST,PUT,DELETE,TRACE,OPTIONS,PATCH";

    @Override
    public void addCorsMappings(final CorsRegistry registry) {
        registry.addMapping("/api/**")
            .allowedMethods(ALLOWED_METHOD_NAMES.split(","))
            .exposedHeaders(HttpHeaders.LOCATION);
    }
}

 

 

2. 인터셉터에서 OPTIONS 메서드 요청이라면 토큰이 없어도 예외를 반환하지 않도록 설정합니다.

@Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String token = AuthorizationExtractor.extract(request);

				// OPTIONS 요청이라면 항상 허용하도록 설정
        if (HttpMethod.OPTIONS.matches(request.getMethod())) {
            setUsernameAndReturn(request, token);
        }

        if (!jwtTokenProvider.validateToken(token) || token == null) {
            throw new AuthorizationException();
        }

        return setUsernameAndReturn(request, token);
    }