AI 법률 상담 서비스(스스Law, SSL)를 개발하던 중 로그인/회원가입 과정에서 문제가 발생했다.
아래는 문제를 해결하면서 노션에 정리했던 내용을 다듬은 것이다.
SSL의 로그인/회원가입은 Spring Security + OAuth 2.0 + JWT를 사용해서 소셜 로그인으로 구현했다.
소셜 로그인을 구현해 본 것은 처음이라 소셜 로그인 또한 자체 로그인 시스템과 같이 JWT를 Header에 담아서 클라이언트에 전달해 줄 수 있을 것이라고 생각했다.
그러나 소셜 로그인의 경우에는 회원가입/로그인 과정에서 페이지가 강제 Redirect되기 때문에 JWT를 Header에 담아서 전달해도 프론트엔드 단에서 이를 받아서 활용하는데 어려움이 있었다. 이에 방법을 찾아보니 일반적으로 OAuth 2.0 소셜 로그인은 JWT를 Cookie에 담아서 전달해준다.
쿠키 설정 시도 - 실패
/* 프론트엔드 서버로 JWT를 전달할 때 Cookie 방식 사용 */
private Cookie createCookie(String key, String value) {
Cookie cookie = new Cookie(key, value);
cookie.setPath("/");
cookie.setHttpOnly(true);
cookie.setDomain(DOMAIN);
cookie.setSecure(true);
return cookie;
}
위처럼 key: 토큰 이름, value: 토큰값 을 설정한 쿠키를 만들어서 전송해줬다.
그러나
로컬 개발 환경에서 테스트 할 때는 Cookie 값이 잘 넘어왔지만 배포 환경에서는 Cookie 값이 프론트엔드 단으로 넘어가지 않는 문제가 여전했다.
CORS 설정 문제일 가능성도 존재했으나 CORS 설정에는 문제가 없었다.
CORS 설정
private CorsConfigurationSource corsConfigurationSource() {
return request -> {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedHeaders(Collections.singletonList(allowedOrigins));
config.setAllowedMethods(Collections.singletonList("*"));
config.setAllowedOriginPatterns(Collections.singletonList("*"));
config.setAllowCredentials(true);
config.setAllowedHeaders(Arrays.asList("Authorization", "Authorization-refresh", "Cache-Control", "Content-Type"));
config.setMaxAge(3600L);
/* 응답 헤더 설정 추가*/
config.setExposedHeaders(Collections.singletonList("Authorization"));
config.setExposedHeaders(Collections.singletonList("Authorization-refresh"));
config.setExposedHeaders(Collections.singletonList("Set-Cookie"));
return config;
};
}
프론트엔드와 백엔드 부분 모두 credentials 설정을 했기에 이 또한 문제가 아니었다.
문제를 해결하기 위해 하루를 꼬박 썼고 결국 문제의 원인을 찾았다
그렇다면 대체 무엇이 문제였을까?
문제점은 백엔드, 프론트엔드가 아닌 이외의 부분에 있었다.
2020.2.4 구글 크롬(Google Chrome) 80버전으로 업데이트되면서 크롬에 새로운 쿠키 정책이 적용되었다. 내용은 Cookie의 SameSite 속성의 기본 값이 "None"에서 "Lax"로 보안등급을 상향 조절하는 것이다.
바로 이것이 문제였다. 그렇다면 SameSite 옵션이란 무엇일까?
SameSite 옵션이란?
이 중 None, Lax 옵션만 간단하게 알아보자.
SameSite의 기준?
web.dev라는 사이트의 일부로 Same-SitePublic 접미사 List
https://publicsuffix.org/list/public_suffix_list.dat
SameSite에 대한 자세한 글은 아래 Google Developers의 글을 읽어보면 도움이 된다.
https://developers.google.com/search/blog/2020/01/get-ready-for-new-samesitenone-secure?hl=ko
SSL 프로젝트의 백엔드, 프론트엔드 경로는 아래와 같다.
그렇다. SSL은 SameSite의 기본 옵션 Lax에 위반되고 있었기에 쿠키를 주고 받지 못했던 것이다.
문제 해결 방법은 간단하다. Cookie를 만들 때 Secure 설정을 Default 값인 Lax에서 None으로 변경해주면 된다.
주의!!!!
Cookie의 Secure 설정을 None으로 변경한다면 CSRF, 쿠키 탈취, 불필요한 크로스 사이트 쿠키 노출 등 여러 보안 문제가 발생할 수 있다.
나는 현재는 테스트 환경이기에 1차적으로 HTTPS를 적용해 모든 요청이 HTTPS를 통해 전송되도록 설정한 것으로 끝냈지만 정식 배포를 한다면 SameSite를 유지하고 Secure 설정을 변경하지 않도록 하는 것이 좋다.
쿠키 설정 시도 - 성공
/* 프론트엔드 서버로 JWT를 전달할 때 Cookie 방식 사용 */
private String createCookie(String key, String value) {
ResponseCookie cookie = ResponseCookie.from(key, value)
.path("/")
.sameSite("None")
.httpOnly(true)
.domain(DOMAIN)
.secure(true)
.build();
return cookie.toString();
}
위처럼 sameSite 옵션을 None으로 설정하고 테스트를 하니 쿠키 전달이 정상적으로 성공했다.
출처
https://cherish-it.tistory.com/12
https://developers.google.com/search/blog/2020/01/get-ready-for-new-samesitenone-secure?hl=ko
https://publicsuffix.org/list/public_suffix_list.dat