이 글에서 설명하는 것
이 글에서는 CORS가 발생했던 이유와 원인을 다룬다. 또한 스프링 CORS 설정, Nginx 리버스 프록시 설정을 통해서 이를 해결하고 curl을 이용해서 CORS 문제가 해결되었는지 확인하는 과정 또한 살펴본다.
참고로 이 글에서 다루는 스프링 CORS 설정은 스프링 시큐리티와 연관되어 있다.
+ CORS란?
https://developer.mozilla.org/ko/docs/Web/HTTP/CORS
문제의 발생
React 개발자가 로컬에서 개발 서버로 요청을 보냈는데 CORS 에러가 발생했다고 한다. 그래서 간단하게 Access-Control-Allow-Origin 헤더를 "*" 로 설정하여 전체 오리진 요청을 허용하려고 했지만 그로 인해 또다시 문제가 발생했다.
https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors/CORSNotSupportingCredentials
알고보니 Access-Control-Allow-Origin 을 전체 허용으로 설정하면 자격 증명을 허용하지 않도록 제재하고 있다. 즉 귀찮다고 서버 측에서 Access-Control-Allow-Origin 헤더를 "*"로 설정하면 쿠키 기반 인증 과정에 문제가 발생할 수 있다.
음 ~ 귀찮아. 그러면 임시방편으로 개발자의 Origin을 특정해서 허용하는 건 어떨까? 물론 정말 바보 같은 짓이다.
한 가지 확실한 건 서버에게 Origin을 속이면 된다는 것이다. 그럼 그 행위를 클라이언트에서 해야 할까?
근데 클라이언트도 Origin을 맘대로 못 바꾼다. (그게 됐으면 CORS 정책이 사실 의미가 없는 거지) 왜냐하면 Origin 헤더는 Header type이 Forbidden header name(?)이라는 프로그래밍적으로 변경할 수 없는 헤더 타입이다. 관련 링크를 첨부한다.
https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_header_name
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin
안 되는 방법은 그만 얘기하고 되는 방법을 얘기해보자. 결론은 Nginx를 사용하면 된다. 즉 Nginx를 리버스 프록시로 사용한다면, 요청이 Nginx로 먼저 들어갈 것이고 그때 Origin을 조작해서 CORS 문제를 회피하면 된다.
👍 이 아이디어는 완벽하게 동작했고 CORS에러를 회피할 수 있었다.
(다음에 리버스 프록시에 대해서 더 알아보자)
https://www.lesstif.com/system-admin/forward-proxy-reverse-proxy-21430345.html
그럼 우선 스프링에서 CORS문제를 해결하는 방법을 살펴보자. (스프링 시큐리티 환경)
참고로 필자는 현재 스프링 시큐리티를 사용하고 있어서 한 가지 주의할 것이 있다. HTTP 요청이 오기 전에 pre-flight 요청이 올 것인데, 그때는 쿠키가 포함되어 있지 않을 것이다. 따라서 인증이 필요한 요청이라면 Spring Security는 사용자 요청을 거부할 것이다. 이를 해결하기 위해서는 스프링 시큐리티 이전에 CORS를 처리해야 하고 가장 간단한 방법은 CorsFilter를 사용하는 것이다.
+ pre-flight 요청이란
https://developer.mozilla.org/ko/docs/Glossary/Preflight_request
그리고 CorsFilter를 적용하는 가장 간단한 방법은 CorsConfigurationSource를 사용하는 것이다. 필자는 다음과 같이 적용했다.
@Configuration
@EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final String[] freeRequestsUrl = {
"/auth/login", "/auth/sign", "/auth/cookie"
};
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic().disable()
.csrf().disable()
.cors()
.and()
.authorizeRequests()
.antMatchers(freeRequestsUrl).permitAll()
.anyRequest().authenticated();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("http://localhost:3000"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(Arrays.asList("authorization", "content-type", "x-auth-token"));
configuration.setExposedHeaders(Arrays.asList("set-cookie"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
- 우선 CorsConfiguration 객체를 생성한다.
- setAllowedOrigins 메서드를 사용해서 허용할 Origin(Request 출저)을 특정해준다. 필자는 localhost:3000으로부터 온 HTTP Request만 허용한 상태이다.
- setAllowedMethods 메서드를 사용해서 허용할 메서드를 정해준다. 필자는 모든 메서드를 허용했다.
- setAllowedHeaders 메서드를 사용해서 허용할 헤더를 정해준다. 필자는 세 가지 헤더를 허용해줬다.
- setExposedHeaders 메서드를 사용해서 set-cookie 헤더를 허용해준다. 그래야 브라우저에서 cookie를 저장할 수 있다.
- 그리고 중요한 setAllowCredentials 메서드를 사용해서 자격 증명을 사용할 수 있도록 해주자.
- 다음으로 UrlBasedCorsConfigurationSource를 생성하여 만들어둔 CorsConfiguration을 모든 url에 적용해주자.
- 해당 객체를 반환하면 끝이다!
+ 스프링 CORS 설정 reference
https://docs.spring.io/spring-security/site/docs/4.2.x/reference/html/cors.html
스프링에서 해줄 수 있는 설정은 이걸로 모두 끝이다. 이제 Nginx 설정을 진행해보자. (Nginx가 설치되어 있다는 가정하에 진행)
해당 설정 파일은 /etc/nginx/conf.d/basic.conf에 설정했다.
server {
listen 80;
server_name example.com;
location / {
root /root/project/frontend/build;
index index.html;
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://localhost:8080/;
proxy_set_header Origin http://localhost:3000;
}
}
- server_name이 example.com으로 되어있는데 본인의 사이트 주소로 변경해주자.
- 나는 공개 api 서버를 원했으니 example.com/api/... 으로 요청이 들어오면 백엔드 서버로 요청이 가도록 설정해주었다. location에 /api/ 라고 되어있고 proxy_pass에 http://localhost:8080/; 으로 되어있는데 이렇게 하면 요청 url을 다음과 같이 재설정해준다.
- Request = example.com/api/auth/login 이라면
- Result = example.com/auth/login 으로 변경된다.
- 또한 proxy_set_header를 사용해서 Origin을 http://localhost:3000으로 변경해주었다. 당연하지만 이걸 해주지 않으면 CORS에러가 발생할 것이다. (확인해보고 싶지 않은가? 그래서 내가 해봄)
만약 Origin을 변경해주지 않으면 정말 안될까? 그래서 해봤다.
(앞서 진행한 스프링 CORS 설정에서는 http://localhost:3000 Origin만 허용하게 되어있다)
테스트 방법은 Curl을 이용했으며 명령어는 다음과 같다. (명령어는 사이에는 원래 줄 바꿈이 없다)
(verbose 뒤에 url은 본인이 테스트하고 싶은 사이트 주소를 이용)
curl
-H "Origin: http://sampleorigin.com"
-H "Access-Control-Request-Method: GET"
GET
--verbose http://example.com/api/auth/login
예상되는 테스트 결과는 당연히 CORS 에러가 나야 한다.
nginx 설정을 다시 정상적으로 설정하고 재시도해보자. 똑같은 요청을 보냈다.
CORS 에러 없이 정상적으로 요청이 수행되었다.
이렇게 CORS에러를 해결해보았다.
근데 쿠키가 저장되지 않는 문제가 발생했다. 아무래도 쿠키의 Same-site 문제인 듯하다. 왜냐하면 유명 브라우저들이 쿠키의 기본 Same-site 설정을 none에서 lax로 변경했다는데 그래서 문제가 발생하는 것 같았다.
그래서 문제를 해결하는 과정에서 certbot을 사용하다가 커널 업데이트를 진행했는데 또 문제가 터지고 말았다.
(커널 업데이트하면 왜 서버 사용이 불가능한 건지.. 잘 이해가 안 된다)
서버는 새로 생성해서 다시 설정해야 할 것 같고.. 이 문제 해결을 위해서 어떤 식으로 접근하고 해결해야 하는지 제대로 알아보자.
'Spring' 카테고리의 다른 글
자동, 수동의 올바른 실무 운영 기준 (0) | 2022.04.08 |
---|---|
같은 클래스 빈이 여러 개일 때 (0) | 2022.04.07 |
생성자 주입과 스프링 (0) | 2022.04.06 |
스프링 컴포넌트 스캔 (0) | 2022.04.03 |
싱글톤의 단점과 스프링의 싱글톤 컨테이너 (0) | 2022.04.03 |