CORS 이슈는 프론트 서버와 백엔드 서버가 분리되어 있다면 1번쯤은 꼭 만나는 이슈이다.
CORS를 먼저 이야기하기전에 SOP에 대해서 이야기해 볼 예정이다...
● SOP (Same Origin Policy)
말 그대로 "같은 출처 정책"이다.
출처?????? 출처가 무엇인가?
Origin (출처)란?
- URI 스키마 (http, https)
- 호스트 (person.jjhserverworld.pe.kr, localhost, naver.com... )
- 포트 (8080, 18080, 80)
위 3가지의 조합이 출처 이다.!!
먼저, Spring-boot에서는 기본적으로 아무것도 설정하지 않는다면 SOP 정책을 따른다.
즉, 자바스크립트 엔진 표준 스펙의 정책으로
호스트, 포트, 프로토콜이 같은 요청에만 접근이 가능한 정책(Policy)이다.
예를 들면, Spring-boot-에서 기본 실행 포트는 8080이다.
React에서 기본실행 포트는 3000이다.
Spring-boot, React를 로컬에서 서비스로 올려서 React에서 Spring-boot로 XMLHttpReqeust로 리소스를 요청하게 되면 아래와 같은 CORS 이슈를 만나게 될 것이다.
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource
같은 호스트, 프로토콜은 맞지만!! 포트가 달라서 다른 출처에서의 요청이므로 CORS정책을 허용해달라는 메시지이다.
● CORS (Cross-Origin Resource Sharing)
말 그대로!! 교차 리소스 공유 정책이다.
여기서는 CORS의 작동방식에 대해서는 간단히만 다룰 것이다.
CORS 동작
1. HTTP 요청 시에, 프론트엔드는 Header에 Origin 출처 정보 (프로토콜, 호스트, 포트)를 서버에 같이 전송.
2. 백엔드 서버에서는 "Accee-Control-Allow-Origin"이라는 Header안에 백엔드에서 허용하는 출처 정보를 알려주고
3. 요청 시에 프론트엔드에서 전송해준 Origin과 백엔드에서 허용하는 Origin을 비교하여 이 요청을 허용할 건지 허용하지 않을 것인지 확인하여 응답해준다.
4. 허용한다면 200 OK, 허용하지 않는다면 다른 에러코드로 CORS 정책 허용 에러 메시지를 남긴다.
● 실습
Spring-boot
@RestController
public class CorsController {
@GetMapping("cors")
public String cors(){
return "cors test";
}
}
JavaScript
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>cors</h1>
<script src="/webjars/jquery/3.7/dist/jquery.min.js"></script>
<script>
$(function (){
$.ajax("http://localhost:8080/cors")
.done(function (value){
console.log("성공");
})
.fail(function (){
console.log("실패");
});
})
</script>
</body>
</html>
XMLHttpRequst 요청으로 "/cors"를 호출해본다. 브라우저 개발자 도구를 통해 Console창을 보면
아래와 같은 CORS 정책이 막혀있다는 메시지가 나온다....
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource
● 해결법
1. @CrossOrigin 어노테이션 사용.
메소드 위에 @CrossOrigin 어노테이션을 활용하여 허용할 Origin을 명시해줄 수 있다.
@RestController
public class CorsController {
@CrossOrigin(origins = "http://localhost:3000")
@GetMapping("cors")
public String cors(){
return "cors test";
}
}
2. WebMvcConfigurer로 전역적으로 설정.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry){
registry.addMapping("/**")
.allowedOrigins("http://localhost:3000");
}
}
3. SpringSecurity Config
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// token을 사용하는 방식이기 때문에 csrf를 disable합니다.
.csrf().disable()
.formLogin() .disable()
// enable h2-console
.and()
.headers()
.frameOptions()
.sameOrigin() // 동일 도메인에서는 iframe 접근 가능
// 세션을 사용하지 않기 때문에 STATELESS로 설정
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.cors().configurationSource(corsConfigurationSource())
/**
* URI별 인가 정보 셋팅.
*/
.and()
.authorizeRequests()
.requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
.anyRequest().authenticated() // 그 외 나머지 리소스들은 무조건 인증을 완료해야 접근 가능
.and()
//AuthenticationFilterChain- UsernamePasswordAuthenticationFilter 전에 실행될 커스텀 필터 등록
.addFilterBefore(new AuthorizationCheckFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class);
}
● 마무리
CORS는 에러가 아니고 리소스 자원을 보호하기 위한 정책수단 중 하나이다.
CORS 문제에 대해서 의문점이 하나 있었다 백엔드 서버를 postman과 같은 툴로 직접적으로 호출할 때는 CORS이슈가 전혀 없었다.
왜 그런가 찾아보니 CORS는 브라우저에서 보안 정책상 사용되는 정책 중 하나인 것 같다.
앞으로는 이 CORS에 대해 친숙해져야 할 것 같다.
'Java > SpringBoot' 카테고리의 다른 글
[SpringBoot] Exception Handler 예외를 통합관리 하자.!!! (0) | 2022.04.15 |
---|---|
[SpringBoot] Log4j2 기본 사용법 (0) | 2022.03.07 |
[SpringBoot] Security 인증 절차 시 DB Access 여러번 일어나는 이슈. (0) | 2022.01.12 |
[SpringBoot] UserDetailsService UserNotFoundException 안되는 이유 (0) | 2022.01.11 |
[SpringBoot] 종속성 순환 에러 트러블슈팅 (0) | 2021.12.30 |