본문 바로가기
Java

자바 스프링 CORS error 해결하기

by 돈민찌 2021. 11. 27.
반응형

안녕하세요. 오늘은 웹개발에서 빈번하게 발생하는, 괴발자들을 개롭히는 CrossOrigin 문제를 해결하는 방법을 알아보겠습니다. 요즘은 웹개발의 추세가 지금 공부하고 있는 프로젝트처럼 백엔드와 프론트엔드, 더하자면 데이터베이스까지 각각 별개로 배포하고 운영하는 MSA 아키텍쳐가 되다보니, 서버와 프론트의 포트 혹은 도메인이 달라 교차 출처 리소스 공유 에러가 개발자들을 괴롭히는 일이 많았습니다. (그렇다고 CORS 없는 웹 생태계를 상상하고 싶지는 않네요.. 지난 프로젝트에서 "요기요"사이트의 강력한 api를 슬쩍 사용해봤었는데, 오픈 api도 아닌 api가 이렇게 쉽게 찬탈 가능한 것은 상당히 보안에 취약해 보입니다.) 자바 스프링이라는 강력한 친구를 공부 중에 스프링에서는 이 문제를 어떻게 해결하고 있는지 보겠습니다.

 

CORS는 왜 이렇게 우리를 힘들게 하는걸까?

이번 포스팅에서는 웹 개발자라면 한번쯤은 얻어맞아 봤을 법한 정책에 대한 이야기를 해보려고 한다. 사실 웹 개발을 하다보면 CORS 정책 위반으로 인해 에러가 발생하는 상황은 굉장히 흔해서

evan-moon.github.io

처음 이 문제를 구글링을 통해 해결하려고 하니까 크게는 세가지 정도 방법이 있는 것으로 나타났습니다.

1. 직접 서버의 응답 헤더에 Access-Control-Allow-Origin 설정하기
2. CrossOrigin 어노테이션으로 특정 호스트,포트 허용하기
3. WebMvcConfig 설정으로 애플리케이션 단위에서 해결하기

각 방법에 대해 탐구해보겠습니다.

1. 서버의 응답을 보내기 전에 Access-Control-Allow-Origin 헤더 싣는 필터 작성하기

https://wonit.tistory.com/572 님의 포스팅을 참고했습니다.

import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CorsFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        response.setHeader("Access-Control-Allow-Origin", "http://localhost:5500");
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Methods","*");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers",
                "Origin, X-Requested-With, Content-Type, Accept, Authorization");

        if("OPTIONS".equalsIgnoreCase(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
        }else {
            chain.doFilter(req, res);
        }
    }

    @Override
    public void destroy() {

    }
}

 보시다시피 실제로 응답 헤더에 실릴 값들을 모두 설정해주기 때문에 복잡한 설정이 필요한 경우에 사용하면 좋을 것 같습니다. 저는 좀 코드가 길고 복잡하게 느껴집니다.

2. CrossOrigin 어노테이션 활용하기

Controller의 클래스 상단에, 혹은 내부의 맵핑 메소드 상단에 CrossOrigin(origins = "도메인")을 어노테이션으로 작성해줍니다.

@CrossOrigin(origins = "http://127.0.0.1:5500/")  // 컨트롤러 클래스의 상단
@RequiredArgsConstructor
@RestController
public class ArticleRestController {

    public final ArticleRepository articleRepository;
    public final ArticleService articleService;
    public final LocationDistance location;

    @CrossOrigin(origins = "http://127.0.0.1:5500/")  // 컨트롤러 맵핑 메소드 상단
    @GetMapping("/api/articles/{query}")
    public ResponseEntity<List<Article>> getArticles (@PathVariable("query") String query) {
        List<Article> articles = articleRepository.findAllByTitleContains(query);
        return ResponseEntity.ok().body(articles);
    }
}

1번 코드보다 간결하고, 사실상 가장 중요한 부분인 "Access-Control-Allow-Origin"에 대한 값만 설정해줘도 잘 동작하는 점이 매우 좋은 것 같습니다. 그리고 어노테이션 형태라 사용할 곳에 붙여 쓰기만 하면 되니 편합니다. 메소드 단위로 입력할 수 있다는 것은 AWS Lambda로 API를 개발하거나 할 때 유용하겠네요.

3. WebMvcConfig 클래스 활용하기

저의 Pick을 받은 방법입니다. WebMvcConfigurer를 사용하는데, 이것도 두가지 방법으로 사용할 수 있습니다.

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("http://localhost:5500", "http://127.0.0.1:5500")
                .allowedMethods("POST", "PUT", "GET", "HEAD", "OPTIONS", "DELETE");
    }
}

WebMvcConfigurer를 구현한(implement) 클래스를 만들고, @Configuration 어노테이션으로 어플리케이션에 연결하는 방법입니다. allowedOrigins(), allowedMethods() 외에도 체이닝으로 연결할 수 있는 메소드가 많습니다.

.allowedOrigins()
.allowedMethods()
.allowCredentials()
.allowedHeaders()
.exposedHeaders()
.allowedOriginPatterns()

두번째는 이 방식 그대로 Configuration 클래스를 만들지 않고 application 메인클래스 하단에 직접 삽입해주는 방법입니다.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@EnableJpaAuditing
@SpringBootApplication
public class ExampleApplication {

    public static void main(String[] args) {
        SpringApplication.run(ExampleApplication.class, args);
    }

    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**")
                        .allowedOrigins("http:/localhost:5500");
            }
        };
    }
}

코드 가독성을 해치지 않는다면 굳이 클래스를 추가로 만들어 쓰는 것보다 이 방법이 나을 수 있겠습니다. 좀 더 복잡한 CORS 정책을 구현하려고 할때는 따로 클래스 파일을 만드는 게 보기 좋을 것 같구요.

요즘 개발일지를 너무 안써서 손 풀기로 한번 다뤄봤습니다. 참고로 위에서 계속 사용하는 5500포트는 VSCode의 Live Server 확장이 사용하는 포트 번호입니다. 여러분들 필요하신 포트 번호로 맞춰 쓰셔요!!

반응형

댓글