Issue
졸업프로젝트를 하는 도중에 클라이언트 (React)를 담당하는 동기에게 연락이 왔다.
" CORS 문제가 나서 서버랑 http 통신을 할 수 없다! "
예전 프로젝트에서도 CORS 문제를 코드 하나로 간단히 해결해 준 경험이 있기에, 코드 하나 추가하고 배포해야지~ 하는 마인드로 CORS를 설정하는 코드를 쓰고 배포했지만
결과는 실패!
(긍정) SOP 먼저 알고?
Single-Origin Policy 즉, 동일 출처 정책
같은 출처를 가진 것들만 리소스들을 공유라는 현상을 이야기 한다.
App A가 서버1에 위치하고 origin이 http://a.com:8080
App B도 마찬가지로 서버1에 위치하고 다른 port를 사용하고 origin이 http://a.com:8081
이라고 한다면 둘은 서버1에 같이 위치하고 있기 때문에 SOP 정책을 지키고 있는 것이라고 볼수 있다.
(부정) CORS란?
Cross-Origin Resource Sharing 즉, 교차 출처 리소스
다른 서버의 리소스를 가져오는, 공유하는 현상을 이야기 한다. -> 공유 하니까 좋아보이는 거 같지만 지양하는 것이다!!
대부분 CORS로 인한 문제는 CORS 정책을 위반했기 때문에 발생한다.
origin은 클라이언트에서 location.origin을 js로 찍어보면 나온다
App A가 서버1에 위치하고 origin이 http://a.com
App B가 서버2에 위치하고 origin이 http://b.kr
이라고 가정한다면 A가 B의 리소스를 가져다가 사용한다는 이것은 CORS 정책을 위반한다고 보면 된다.
(CORS가 부정적 의미인데 위반한다고 하니까 처음에 헷갈렸는데 그냥 그렇게 생각하자... 영어다)
ORIGIN? 출처?
어디에서 리소스를 가져오느냐이기 때문에 결국 출처는 uri라고 보면된다
https://codinggyun.tistory.com/manage/newpost
https - protocol : ssl이 적용된 https도 있다
codinggyun.tistody.com - Host : 서버 네임이다. 도메인을 적용하지 않으면 ip이기도 하다.
/manage/newpost - path : 리소스의 위치이다. 서버에서는 해당 경로를 api의 동작에 맞게 지정, 설정해줄 수 있다.
SOP, CORS 정책을 지키는 이유?
결국은 보안 때문이다. 여러가지 앱들이 소통할 때, 다른 서버의 리소스를 가지고 오면 중간에 해커들이
CSRF나 XSS 같은 해킹 방법을 이용하여 정보를 가로채거나 심을 수 있다.
그럼 지키려고 어떻게 하는데? - Preflight Request
브라우저는 노력한다. CORS를 위반하지 않기 위해!
모든 브라우저는 request를 보내기 전에 CORS 정책을 위반했는지, default로 preflight request를 보낸다.
- 접근하려는 origin/자원에 브라우저(클라이언트 app)의 origin이 무엇인지 OPTION 메소드로 보낸다.
- 서버에서는 해당 Origin이 나랑 다르더라도 내 자원에 접근할 수 있는 것으로 설정되어 있으면 OK를 response 준다. 이때, OK 응답에는 cross 접근이 허용 가능한 origin을 같이 header에 넣어서 준다
- preflight request가 성공했으면, request를 보낸다
Solution
Spring WebMvcConfigurer 설정하기
서버에서 preflight request를 처리하는 모듈에서 클라이언트의 origin을 허용한다는 것을 넣으면 된다
spring boot initializer로 프로젝트를 세팅했다면
applicationMain에서 bean으로 config overriding하여 설정해 줄 수 있다.
@EnableJpaAuditing
@SpringBootApplication(exclude = SecurityAutoConfiguration.class)
public class OttAllApplication {
public static void main(String[] args) {
SpringApplication.run(OttAllApplication.class, args);
}
@Bean
public WebMvcConfigurer corsConfigurer(){
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://블라블라.amazonaws.com")
.allowedMethods("*");
}
};
}
}
- @Bean : WebMvcConfigurer를 bean으로 생성해서 싱글톤으로 처리할 수 있다
- addCorsMappings : WebMvcConfigurer에서 오버라이딩 할 함수로 CORS 관련 설정을 할 수 있다.
- addMapping : 서버의 어느 자원까지 해당 설정을 적용할 것인지 설정할 수 있다. /**는 호스트 네임 뒤에 어떤 것이 나오든 오케이라는 표현식. 지금 프로젝트에서는 모든 uri에 대해서 CORS 위반 허용을 해줄 것이다.
- allowedOrigins : 어떤 origin들이 서버에 CORS 위반으로 자원을 접근할 것인지 써주는 곳이다. 이곳에 리액트가 띄워진 서버 uri를 넣었다. 만약 여러개의 origin을 설정하고 싶다면 쉼표를 사이에 두고 다수를 넣으면 된다
- allowedMethods : 특정 method에 한정해서 CORS 위반 설정이 가능하다. 지금 프로젝트에서는 모든 메서드에 대해서 허용한다.
하지만 졸업 프로젝트의 로그인과 회원가입 기능을 제외하고는 모두 CORS 위반으로 오류가 났다!!
분명 allowedOrigins과 addMapping으로 모든 uri와 origins에 대해서 CORS 위반을 허용해줬는데?????
인증에 사용한 Interceptor가..?
졸업 프로젝트는 JWT 인증 방식을 기반으로 서버로 들어오는 request에 대해서 인증 절차를 거치고 있다.
spring interceptor의 실행 시점을 생각해보아야 한다.
request가 들어가는 방향으로 생각했을 때,
Interceptor는 DistpactherServelet의 바로 다음에 실행이 된다.
즉 Spring에서 CORS origin을 처리하는 모듈에 가기 전에 interceptor가 preflight request를 낚아채는 것이다!
request Header에 Jwt 인증 토큰이 들어있는지, 해당 토큰이 유효한지 확인하는 작업은 Interceptor에서 진행한다!
- OPTIONS 메소드로 온 preflight request도 마찬가지로 interceptor가 header를 뜯어보고 토큰을 확인한다
- preflight request이기 때문에 토큰 담고 있는 헤더도 없고, 토큰도 없다
- interceptor가 false 처리되어서 bad request를 반환한다
Interceptor에서 OPTIONS 메서드의 request 예외 주기
OPTIONS로 들어오는 request는 브라우저가 preflight request로 보내는 것이기 때문에
아래의 설정을 통해 해당 request일 때, interceptor가 통과를 해주게 설정한다.
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String accessToken = jwtUtil.getJwt();
if(request.getMethod().equals("OPTIONS")){
return true;
}
..... JWT 인증을 위한 코드
이렇게 React 앱에서 즉, 브라우저에서 보내는 CORS 정책 위반을 체크하기 위한 preflight request를 처리함으로써
CORS 위반으로 생기는 오류를 해결할 수 있었다.
'백엔드 Issue 해결' 카테고리의 다른 글
next (query string) url의 redirect 예외처리 (0) | 2021.12.02 |
---|