들어가며

Tweaver 기능 중에는 OAuth2 기반의 소셜로그인 기능이 있다. 본 프로젝트에 해당 기능을 넣게된 계기와 마주한 상황, 그리고 어떻게 해결했는지를 기록하려 한다.


 

기대점과 문제점

Twaver는 다양한 사람이 만나 당일치기 여행을 계획하는데에 도움을 주는 서비스이다. 서비스 이용자는 당일치기 여행을 계획하는 사람을 찾기 위해 빠르게 서비스에 접근할 수 있어야 한다. 따라서 이용자가 손쉽고 간편하게 서비스에 접근할 수 있게 소셜 로그인 기능을 추가했다.

동작과정을 학습해볼겸, 다양한 소셜로그인을 구현하기 전에 먼저 카카오 로그인만 구현해보기로 했다.

소셜 로그인을 구현하기 위해 Spring Security와 OAuth2를 이용했다. 여기서 필수적으로 고민해 볼 것은 프론트엔드와 백엔드의 역할을 어떻게 가져갈 것인지다. 나는 기능 구현 당시에 백엔드에서 전부 처리하는 방식으로 구현했다. 그렇게 기능을 테스트해보니 프론트에서 다양한 문제가 발생했고, 이를 공유하며 많은 것들을 배울 수 있었다.

 


OAuth2 - 카카오 로그인

우선 OAuth2의 Grant Type을 알아야 한다. 이 링크를 확인하면 된다. 카카오는 grant_type이 authorization_code 타입이다. 해당 타입은 다음의 과정을 거친다.

  1. 인가서버에 code를 요청한다. 
  2. 사용자(Resource Owner)의 동의하에 인가서버가 요청을 보낸 클라이언트(Tweaver 서버)에게 코드를 발급한다.
    • 이때, 로그인이 안되어 있다면 로그인 페이지로 이동한다.
    • 로그인 성공시 클라이언트는 사용자로부터 권한 동의를 받는다. (권한 동의 페이지로 이동)
    • 동의 완료 후, 인가서버는 클라이언트로 리다이렉트한다. (코드발급)
  3. 클라이언트의 권한 부여 승인의 결과로 토큰을 획득한다. 이때 인가서버는 redirect_uri?code=ua01...&state=eia3d9... 를 보내준다.
    • code: 인가서버는 쿼리 스트링으로 인증 코드를 반환
    • state: 인가서버는 승인요청에서 전달한 것과 동일한 상태 값을 반환 (csrf token과 같은 역할)
    • 클라이언트가 인가서버에 요청을 할때는 POST 요청으로 code, client_id, client_secret, redirect_uri를 요청한다.

 


백엔드 구현 방식

[그림1] SpringCofig 내의 oauth2 처리방식 코드
[그림2] Tweaver 내 OAuth2 흐름도

Tweaver에서 구현한 방식은 다음과 같다. (깃헙 링크: https://github.com/ValueWith/ValueWith_BE)

  1. tweaver 서버에 .../oauth2/authrization/{provider} 요청이 오면 authorizationRequestRepository를 상속받은 HttpCookieOAuth2AuthorizationRequestRepository에서 인증정보를 쿠키에 저장해 카카오 인가서버와 통신을 완료한다.
  2. 통신이 이루어진 후 OAuthUserCustomService에서 tweaver 회원 서비스에 맞게 DB에 저장 후 PrincipalDetail 객체에 담아준다.
  3. 인증이 성공한다면 refreshToken을 발급해 프론트와 합의된 uri에 쿼리스트링으로 보내준다. (oAuth2SuccessHandler)
  4. 인증이 실패한다면 error 메시지를 반환한다. (oAuth2FailureHandler)

 


첫 번째 삽질: CORS

redirect_uri를 프론트엔드로 바꾼뒤 처음으로 발생한 문제는 CORS 에러였다. 개발 당시에는 oauth2 요청을 하면 백엔드에서 쿠키(인가정보)를 갖고 있기 때문에 code요청  redirect uri를 프론트로 하든 백으로 하든 상관 없을거라 생각했다. 하지만 oauth2 로그인이 한 번 요청된 이후 또다시 요청이 들어오면 cors 에러가 등장했다.

[그림3] 에러상황

문제의 원인은 리다이렉트 URI를 따라 프론트엔드 서버로 리다이렉트 된 순간, 한 번 더 :8080/oauth2/authorization 을 호출하면 출처가 :5173으로 되어버린 것이다. :8080에서 보냈지만 출처가 :5173으로 되어있으니 카카오 인가서버에서 CORS Error를 보내는 것이다.

단서는 쿠키다. 원래라면 처리되고 백엔드에서 사라질 쿠키가 프론트 서버로 넘어가버렸다. 프론트엔드에서 쿠키를 확인해보면 oauth2 쿠키가 있는걸 확인할 수 있다. 그래서 혹시나 요청을 받을때 백엔드에서 쿠키를 지워봤지만 쿠키 자체의 문제는 아니었다.

지금까지 확인한 부분은 내가 임의로 프론트엔드 코드를 작성해서 나온 부분이었다. navigate('/') 를 빼서 무한히 redirect가 되는 것도 실험해 봤는데 똑같이 CORS가 뜬다.

function KakaoCodeCallback() {
  const navigate = useNavigate();
  const [searchParams, setSearchParams] = useSearchParams();
  const setIsLogin = useSetRecoilState(loginState);

  const handleKakaoLogin = async () => {
    console.log("kakaoCodeCallback-handleKakaoLogin Working..");

    const code = searchParams.get('code');
    console.log(code);
    const response = await instance.get(
      import.meta.env.VITE_SERVER_URL + '/oauth2/authorization/kakao',
      { params:
        { code: code }
      },
      { withCredentials: true }
    );

    setAccessToken(response.data.accessToken);
    setUserInfo(response.data.loginMemberIdDto);
    setIsLogin(true);

    navigate('/');
  
  }
  useEffect(() => {
    handleKakaoLogin();
  }, []);

  return (
    <div>
      <Loader />
    </div>
  );
}

export default KakaoCodeCallback;

아! code를 따로 전달하지 않아도 CORS가 뜬다.


 

느낀점

[그림4] 에러 내용

이로 미루어 보았을때 리다이렉트 uri가 프론트로 보내졌을 경우, 프론트엔드에서 백엔드로 oauth2 요청을 한 번 더 하면 출처가 프론트엔드로 잡히는 것 같다. 궁금한 부분은 oauth2 재요청시에 어떤 것 때문에 origin이 프론트엔드서버 주소로 된 것일까이다. 처음부터 안될거였으면 맨 처음 oauth2 요청에서 걸려야 하는게 아닌가? 

결론적으로 카카오 로그인 기능을 백엔드에서 모두 처리하는 것으로 구현했다. 하지만 카카오 로그인 기능은 원래 기대하던대로 oauth2의 redirect_uri를 백엔드 서버로 맞춰놓으면 출처가 같아지면서 정상적으로 실행된다.

만약 redirect_uri를 프론트엔드에서 받는다면 백엔드에서 oauth2 요청 절차를 나누어서 구현해야 한다.

BELATED ARTICLES

more