1. 문제상황
- 리뉴얼 과정에서 프론트엔드 프로젝트를 백엔드 서버서로부터 분리하면서 발생한 문제
- 로그인 과정에서 CORS 에러, 쿠키가 전달되지 않는 문제
- 이후 쿠키 Samesite=Lax 이슈 발생
- 위의 문제점들을 해결하기 위해 겪었던 시행착오들과 해결방법을 정리하기 위해 글을 남긴다.
- CORS 에러 예시
2. 시도한 해결방법들
2-1. 스프링 부트 프로젝트에 CORS 허용 로직 추가
- 기존에는 백엔드 톰캣 서버에서 모든 통신이 이루어졌기 때문에 굳이 CORS 를 허용해주지 않아도 됐다.
- 우리 회사 백엔드는 Authentication 을 위해서 Spring Security를 쓰고 있기 때문에, 해당 클래스 안에 아래의 Bean 을 추가해주었다.
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("[도메인]"));
configuration.setAllowedMethods(Arrays.asList("GET","POST","OPTIONS"));
configuration.setAllowedHeaders(Arrays.asList("Authorization", "Cache-Control", "Content-Type"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
2-2. Cookie Samesite=Lax 이슈 발생
- Allow Cross Origin Header 에 통신을 원하는 도메인을 추가하고, CORS 에러를 해결하고 났는데도 쿠키가 오지 않는 문제점이 있었다..
- 분명 쿠키 통신을 위한 credential 옵션도 true 로 변경해줬는데 뭐가 문제였을까 하고, 에러메세지를 잘 살펴보니
- 쿠키의 SameSite 속성과 관련된 오류였다.
- 알고보니 2020년 2월부터 Chrome 브라우저에서 Samsite=Lax 가 기본값으로 변경되어 발생한 문제였다 (그전에는 Cross Origin 통신이 없었기 때문에 해당 이슈가 문제되지 않았던 것^^..)
- 쿠키 속성은 이번 문제점을 해결하면서 새롭게 알게된 사실이기 때문에,관련 내용을 첨부한다.
SameSite =None
- None 은 이전까지 사용되던 Default 정책으로 SameSite를 검증하지 않는다. 따라서 A 사이트에서 B 사이트로 요청을 전송하게 되면, B 사이트에 쿠기가 붙어서 전송된다.
SameSite = Strict
- Strict 는 쿠키의 SameSite 검사를 강하게 제한하는 정책으로 소스가 되는 도메인과 대상 도메인이 일치해야만 쿠기가 포함되어 전송된다.
( O ) www.google.com => www.google.com
( X ) www.hahwul.com => www.google.com
SameSite = Lax
- Lax 는 기존 Strict 정책에서 예외처리가 몇개 추가된 정책이라고 보면된다.
- GET 을 사용하는 요청 중 앵커태그
<a href>
, form 의 get 메소드 <form method=get>
정도만 예외되고 나머지는 Strict 와 동일하게 SameSite 가 아닌 경우 쿠키 전송이 차단된다.
해결방법
- 쿠키를 설정할 때 SameSite 속성에 값을 할당해주면 된다!!
- 나는 직접 Spring Security 에 쿠키 속성을 변경하는 필터를 적용해서 이를 해결하려 했다.
public class CustomFilter extends GenericFilterBean {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletResponse resp = (HttpServletResponse) response;
resp.setHeader("Set-Cookie", "locale=de; HttpOnly; SameSite=strict");
chain.doFilter(request, response);
}
}
3. 최종 해결방법: 아파치 웹서버 해더 변경
- 위와 같이 모든 설정을 적용하고, 로컬에서 테스트 해본결과 쿠키를 담은 HTTP 통신이 성공적으로 이루어지길래 배포해, 로그인을 시도해보았지만... 결과는 실패 ㅠㅠ
- 이와 관련해서 정말 수많은 삽질을 했지만, 결국은 아파치 웹서버에 직접 헤더를 적용함으로써 해결하였다..!
- 우리팀에서는 리눅스 환경안에 아파치 웹서버를 올리고, WAS 로는 스프링 부트의 내장 톰갯을 사용한다. 즉, 클라이언트가 직접 통신을 보내 요청을 처리하는 부분은 아파치 였던 것^^.
- 따라서 아파치의 config 파일에 아래 코드를 추가해서 성공적으로 CORS 문제를 해결할 수 있었다 !!
- 코드
우리는 HTTPS 를 사용하고 있기 때문에, 443 VirtualHost 안에 아래의 코드를 적용해 주었다.
### Chrome SameSite=Lax 디폴트 이슈 해결
Header edit Set-Cookie ^(.*)$ $1;HttpOnly;Secure;SameSite=None
### CORS 에러 해결
Header always set Access-Control-Allow-Origin "[도메인]"
Header always set Access-Control-Allow-Methods "POST, GET, OPTIONS"
Header always set Access-Control-Max-Age "3600"
Header always set Access-Control-Allow-Headers "x-requested-with, Content-Type, origin, authorization, accept, client-security-token"
Header always set Access-Control-Allow-Credentials "true"
RewriteEngine On
### Option preflight 요청 시 마다 항상 200 코드 반환할 수 있도록
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ $1 [R=200,L]
- +) preflight 통신에서 403에러가 나도록하지 않으러면, OPTIONS 메소드 호출시 항상 200 코드를 보내도록 설정해주어야 한다.