문제상황
밖에서 룰루랄라 놀던중
F-E 에게 로그아웃이 안된다는 이야기를 들었다.
한창 프로젝트를 개발하고 테스트를 하던 중이었고,
분명 로그아웃이 잘 동작하던 것을 확인하였는데
왜 안될까? 싶었다
그래서 집으로 돌아와서
로그아웃 테스트를 진행해보았다.
Postman으로 테스트 해보았는데,
너무나도 잘 되는 로그아웃이었다... (배포된 서버에 로그아웃 요청을 보냄)
그래서 다시 프론트한테 물어봤더니
Postman에서는 잘되는데 브라우저에서 요청을 보낼때는 안된다는 것!!!
원인파악
흠 뭐지 싶은데,,,,,,, 문뜩 든 생각이...
로그아웃을 개발할 때는 리프레시 토큰을 없애는 로직을 추가해두었는데,
리프레시 토큰을 쿠키에 저장해두었다.
그래서 쿠키에 있는 리프레시 토큰을 찾고
그 토큰을 없애주는 로직이 들어 있고,
로그아웃은 CustomLogoutFilter로 구현해서 필터단에 존재한다.
그리고 브라우저에서 로그아웃을 할때는,
400에러가 뜨는 상황이라서
로그아웃할때 쿠키값이 제대로 전달되지 않는 문제라고 생각했다.
그래서 프론트한테 요청해서 쿠키값이 제대로 들어갔냐고 물어보니
실제로 쿠키값이 제대로 들어가 있지 않았다.
그렇다는건.... 범인은 단 한명
1. 요청을 보낼때 쿠키값을 넣지 않고 보낸 프론트 개발자
2. 쿠키값이 브라우저에게 제대로 넘기지 않은 백엔드 개발자
결론부터 말하자면 정답은 2번이었다...
Frontend 가 해줘야 할 것
현재 프론트는 ts로 개발하고 있고,
axios를 사용하여 api 요청을 보내고 있다.
그런 상황에서 withCredentials : true 만 설정해두면
브라우저는 알아서 서버로 부터 쿠키값을 전달받고 요청을 보낸다.
그래서 개발자는 withCredentials: true만 설정해주면 된다.
credentials은 쿠키, Authorization 인증헤더, TLS/SSL와 같이
사용자를 식별하거나 권한을 증명하는 데 필요한 데이터를 말한다.
즉, withCredentials : true는
사용자의 인증정보를 서버로 전송한다는 뜻이다.
그런데 프론트 측에서는 이 옵션도 설정되어 있었다.
그래서 프론트는 문제가 없었다!
Backend가 해줘야 할 것
@Override
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList(
"http://localhost:3000",
"프론트 서버 url"
));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowCredentials(true);
configuration.setAllowedHeaders(Collections.singletonList("*"));
configuration.setExposedHeaders(Arrays.asList("Authorization", "access"));
return configuration;
}
이건 cors를 처리해준 spring 코드이다.
cors 문제가 발생하지 않기 위해서
Access-Control-Allow-Origin 설정을 통해서
react는 로컬에서는 3000번 포트, 배포된 프론트 서버 url 접근도 허용해주었다.
그래서 cors문제는 아니다.
또한 브라우저에서 보낸 사용자 정보를 처리하기 위해서
서버측에서도
Access-Control-Allow-Credentials 도 true로 설정해줘야 한다.
하지만 이것도 설정되어 있어서
Credentials 문제도 아니었다.
문제 발견
그래서 모든걸 허용해주었는데 왜 안될까 하면서 구글링을 하다보니
SameSite라는것을 알게 되었다.
이 값을 none으로 설정해주면 브라우저에서 쿠키를 담아서 요청을 보낼수 있다고 하길래
SameSite에 대해서 알아보게 되었다.
SameSite 속성
브라우저의 SameSite 속성은 쿠키에 설정할 수 있는 옵션이다.
쿠키가 어떤 상황에서 서버로 전송되는지 제어할 수 있다.
그래서 CSRF 공격을 방지하고, 사용자 프라이버시를 보호하기 위해서 도입되었다.
SameSite 속성에는 주로 3가지 옵션이 존재한다.
아래 설명을 작성해두었다.
Strict 옵션 : 쿠키는 동일한 사이트 사이에서만 전송할 수 있다.
예를 들어서 www.naver.com -> www.naver.com 만 쿠키 전송이 가능하고
www.google.com -> www.naver.com 으로 쿠키 전송은 불가능 하다는 것이다.
그래서 로그인 세션 쿠키처럼 민감한 값이 들어간 쿠키값을 담아서 요청보낼때 적합한 옵션이다.
Lax 옵션 : 기본적으로는 Strict 옵션을 따르고, GET 요청에는 예외를 두어서
다른 사이트에서도 쿠키가 전송가능하도록 허용하는 옵션이다.
2020년 이후 브라우저 정책변화로 default 옵션은 Lax이다.
None 옵션 : 쿠키는 동일 사이트(url이 같은 경우) 혹은 크로스사이트 요청(url이 다른 경우)에서도 모두 전송된다.
하지만 이런 경우, Secure속성도 함께 설정해주어야 한다.
즉, 보안이 보장되어있지 않기때문에, https 환경에서만 쿠키를 담아서 요청을 보내도록 허용하는 것이다.
(http 요청은 무시된다)
그래서 배포된 프론트 서버에서만 요청이 전송되고
로컬에서는 요청이 보내지지 않는다.
그래서 나는 SameSite 옵션을 None으로 설정하고
secure 옵션을 true로 설정해두었다.
그래서 아래와 같이 설정해주었다.
public static Cookie createCookie(String key, String value) {
Cookie cookie = new Cookie(key, value);
cookie.setMaxAge(24*60*60);
cookie.setSecure(true);
cookie.setHttpOnly(true); // 쿠키에 접근할 수 없도록 설정
cookie.setAttribute("SameSite", "None"); // SameSite 속성 설정
cookie.setSecure(true); // https일 경우 설정
// cookie.setPath("/"); // 쿠키의 적용 범위 설정
//js로 쿠키에 접근 못하게 함
cookie.setHttpOnly(true);
return cookie;
}
그랬더니 logout 동작도 잘되고
브라우저에서 쿠키를 담아서 성공적으로 요청을 보내도록 구현하였다!
'Back-End' 카테고리의 다른 글
웹훅 (2) | 2025.04.20 |
---|---|
DTO, DAO, VO 차이점 (0) | 2025.04.11 |
DI 패턴을 사용하여 IoC 설계 원칙을 구현하고 있다 (0) | 2025.03.31 |
Kafka를 사용하는 이유 (0) | 2025.03.28 |
MSA에서 SAGA Pattern을 사용하는 이유 (0) | 2025.03.28 |