스프링 프론트 쿠키가 사라지는 현상 - 정리

이진우·2024년 6월 28일
0

스프링 학습

목록 보기
35/41

현상황

로그인을 시도하고 성공하면 백앤드가 프론트에게 accessToken 과 토큰 재발행을 위한 refresh Token 을 준다.

백앤드가 로그인시 프론트엔드에게 access token 은 헤더에 refresh token 은 쿠키에 담아 보냈지만

재발행을 할 때는 백앤드가 프론트엔드로부터 access token 과 refresh token 을 헤더를 통해 받고 싶은 상황이다.

문제점

프론트엔드와 협업을 하던 도중에 쿠키가 네트워크 탭 및 헤더에 Set-Cookie 가 설정되어 있음에도

프론트에서

const reissueToken = async () => {
    const url = apiAddress + "/api/v1/auth/reissue";
    const refreshToken = Cookie.get("refreshToken");

    console.log("Refresh Token:", refreshToken);

의 출력 결과가 로컬에서 돌릴 떄 refershToken이 undefined 가 출력 되어 있는 모습을 볼 수 있었다.

const response = await fetch(url, {
     method: "POST",
     headers: {
       "accessToken": token,
       "refreshToken": refreshToken,
     },
     credentials: 'include',
   });

위와 같이 refreshToken 을 헤더에 넣어서 보내고 싶은데도 말이다.

이런 문제를 해결했던 과정을 기록해 본다.

이제부터 자바 스크립트로 접근하므로 httponly 는 항상 false , credential 관련된 이슈는 모두 해결한 것으로 간주한다.

실험 1: 스프링 : SameSite 지정 X

스프링에서 쿠키를 만들 때

private ResponseCookie makeResponseCookie(String refreshToken,Long refreshTokenValidationTime){
        return  ResponseCookie.from("refreshToken",refreshToken)
                .httpOnly(false)//   true 시 자바스크립트에서 쿠키 접근 불가 따라서 XSS 공격 방지
                .path("/")
                .maxAge(refreshTokenValidationTime)
                .build();
    }

로그인을 진행한 후 크롬 네트워크 개발자 네트워크 탭을 보면 아래와 같은 주의 표시를 볼 수 있다.

그러면 마우스를 가져다 대면 아래와 같은 문구가 나온다. (요약했습니당)

이 Set- cookie 는 Samesite 속성을 지정하지 않았기 때문에 
Samesite = Lax 로 기본 설정이 된다. 
또한 크로스 사이트에서 응답이 발생하여 크로스 사이트 이동이 가능하게 
Samesite를 None으로 설정해주세요

따라서 스프링에서 SameSite 를 None 으로 설정하고 돌려본다.

private ResponseCookie makeResponseCookie(String refreshToken,Long refreshTokenValidationTime){
        return  ResponseCookie.from("refreshToken",refreshToken)
                .httpOnly(false)//   true 시 자바스크립트에서 쿠키 접근 불가 따라서 XSS 공격 방지
                .path("/")
                .maxAge(refreshTokenValidationTime)
                .sameSite("None")
                .build();
    }

이러고 다시 크롬 개발자 탭에서 Set-Cookie 부분을 확인한다.

확인을 하면 여전히 느낌표 표시가 나온다!

실험 2: Secure: true 설정

그 느낌표 표시의 응답을 요약하면 아래와 같다!

SameSite none 속성은 있지만 SameSite none 을 설정하는데 필요한 Secure 속성은 없으므로 
Set-Cokie 헤더를 통해 쿠키를 설정하려는 시도가 차단되었습니다.

따라서 다시 스프링에서 쿠키를 구울 때

private ResponseCookie makeResponseCookie(String refreshToken,Long refreshTokenValidationTime){
        return  ResponseCookie.from("refreshToken",refreshToken)
                .httpOnly(false)//   true 시 자바스크립트에서 쿠키 접근 불가 따라서 XSS 공격 방지
                .secure(true)//true 시 HTTPS 연결을 통해서만 전달 .
                .path("/")
                .maxAge(refreshTokenValidationTime)
                .sameSite("None")
                .build();
    }

위와 같이 Secure true 로 설정하고 서버를 다시 돌려보자!

여전히 오류가 발생한다.

실험 3 https 적용

오류의 내용은 아래와 같다.

Secure 속성은 있지만 보안 연결을 통해 수신되지 않았으므로 
Set-Cookie 헤더를 통해 쿠키를 설정하려는 시도가 차단되었습니다

이를 해결하기 위해 api 요청을

https 로 보내본다(당연히 백앤드 도메인과 https 적용이 되어 있어야 한다)

그 문제의 느낌표가 사라진다!

또한 이 느낌표가 사라짐과 동시에

스프링의 위 컨트롤러를 통해서 프론트로 부터 보내온 refreshToken 에 접근이 가능하다.

하지만 우리가 원래 token 을 받으려고 했던 방식인

헤더를 통한 접근도 가능할까??

헤더를 통해 접근을 시도할 때의 화면이다.

재발행 버튼을 눌렀을 때

토큰이 담겨져야 하는 Refreshtoken 의 헤더의 값이 undefined 가 나온다.

콘솔 log 로 찍어봐도

const reissueToken = async () => {
    const url = apiAddress + "/api/v1/auth/reissue";
    const refreshToken = Cookie.get("refreshToken");

    console.log("Refresh Token:", refreshToken);

refreshtoken 에 undefined 라는 단어가 출력되는 것을 볼 수 있다.
물론 보안적으로 자바스크립트로 쿠키에 접근하는 것을 허용하는 것은 보안적으로 안좋다고 한다. (httponly)

하지만 헤더를 통해 꼭 받아 보고 싶은 걸?

실험 4 로컬 도메인을 변경

프론트에 https 적용 등등 이것저것 해보다가 단서를 얻었다.
localhost 에는 접근이 안되나 같은 도메인을 이용하고 리버스 프록싱을 적용한
우리 서버에는 무리 없이 접근이 된다는 것이었다.

따라서 domain 에 관련된 문제임을 파악하였다.

스프링 쿠키 수정

private ResponseCookie makeResponseCookie(String refreshToken,Long refreshTokenValidationTime){
        return  ResponseCookie.from("refreshToken",refreshToken)
                .httpOnly(false)//   true 시 자바스크립트에서 쿠키 접근 불가 따라서 XSS 공격 방지
                .secure(true)//true 시 HTTPS 연결을 통해서만 전달 .
                .path("/")
                .maxAge(refreshTokenValidationTime)
                .sameSite("None")
                .domain(".testcre8.co.kr")
                .build();
    }

또한 이를 테스트하기 위한 프론트 측의 local 에서 localhost 대신 다른 도메인 이름으로 수정한다.

이렇게 하면 이제부터 로컬에서 접속하나 저 도메인의 이름으로 들어갈 수 있다고 한다.

local 에서 접속 할 때의 화면이다.

이제는 쿠키로부터 refreshtoken 을 가지고 와서 그것을 헤더에 실어 보낼 수 있다.

정상적으로 refreshToken 이 콘솔에서 출력되고

헤더에도 담겨진 모습을 볼 수 있다.

해결은 했지만 의문점이 많이 남는 항상 로컬에서 돌릴 때 이런 복잡한 과정을 항상 거쳐야 하는지 더 나은 해결방법이 있는지 찾아봐야 겠다...

profile
기록을 통해 실력을 쌓아가자

0개의 댓글