dnd-side-project / dnd-11th-7-backend

모두의 일정을 한눈에! 간편한 일정 조율 서비스, 째깍
12 stars 1 forks source link

카카오 로그인을 성공하였지만 오류 발생 #60

Closed f1v3-dev closed 2 months ago

f1v3-dev commented 2 months ago

🚨 어떤 버그인가요?

WHITE LIST에 등록되어있지 않은 API를 테스트하기 위해서 로그인을 진행해보았습니다.

카카오를 통해 정상적으로 회원 등록이 되어 DB에 저장되고 Refresh Token도 발급되어 Cookie에 저장이 되어있습니다. 또한 응답 헤더에 Authorization에 Bearer 형태로 넘어오는 것 까지 확인을 했어요!

현재 상황에서 정상적으로 로그인이 완료가 된 것인데 저렇게 오류 페이지로 넘어가는 이유가 무엇일까요?

💥 어떤 상황에서 발생한 버그인가요?

image image

💡 예상 결과

로그인 -> 로그인 성공 (응답 헤더에 Bearer 형태의 Access Token, 쿠키에 Refresh Token) -> 메인 페이지로 이동

RTUnu12 commented 2 months ago

아무래도 헤더 값을 받아오지 못하는것 같습니다. check-auth는 헤더를 기준으로 하는데 그 과정에서 문제가 생긴 듯 합니다. 이에 대해 좀 더 살펴볼 필요가 있을거 같네요.

f1v3-dev commented 2 months ago

제가 아까 파악했을 때 302 REDIRECT 과정에서 Authorization header에 담겨서 오더라구요? 그러다 보니까 오류가 발생한걸까요??

현재 배포된 서버에서 크롬 개발자 도구(F12)에서 네트워크쪽 켜놓은 상태에서 로그인 진행해보시면 어떤 요청이 오고갔는지 볼 수 있고 검색 (Ctrl + F)하면 refresh token과 authorization header가 어디서 나오는지 파악이 가능하니까 해당 사항 참고해서 같이 봐봅시다 !

RTUnu12 commented 2 months ago

해결 방법을 찾은 것 같습니다.

  1. MemberController에서 AccessToken을 받아 멤버 정보를 얻는 엔드포인트를 만듭니다. 여기선 AccessToken을 쿠키로 하였습니다.
    @GetMapping("/user-info")
    public ResponseEntity<MemberRequestDto> getUserInfo(@CookieValue("access_token") String accessToken) {
        return ResponseEntity.ok(memberService.findByAccessToken(accessToken));
    }
    public MemberRequestDto findByAccessToken(String accessToken) {
        // String userId = SecurityContextHolder.getContext().getAuthentication().getName();
        // 이게 제일 확실하긴 한데 인증되지 않은 경로로 가면 annoymousUser라고 뜨더라고요...
        String userId = jwtProvider.validate(accessToken);
        log.info("userId = {}", userId);
        Member member = memberRepository.findById(userId)
                .orElseThrow(() -> new IllegalArgumentException("유저를 찾을 수 없습니다."));
        log.info("member = {}", member.getNickname());
        return new MemberRequestDto(member);
    }
  2. SuccessHandler에서 프론트엔드로 리다이렉트를 하지 않고 위의 엔드포인트로 리다이렉트합니다. 이때 쿠키가 설정되어야 합니다. (헤더는 값을 보내지 못합니다...)

    Cookie accessTokenCookie = new Cookie("access_token", accessToken);
        accessTokenCookie.setSecure(true);
        accessTokenCookie.setHttpOnly(true);
        accessTokenCookie.setPath("/"); // 모든 경로에서 접근 가능하도록 설정
        accessTokenCookie.setMaxAge(60 * 60 * 24); // 1일
    
        // Refresh Token 쿠키 설정
        Cookie refreshTokenCookie = new Cookie("refresh_token", refreshToken);
        refreshTokenCookie.setSecure(true);
        refreshTokenCookie.setHttpOnly(true);
        refreshTokenCookie.setPath("/"); // 모든 경로에서 접근 가능하도록 설정
        refreshTokenCookie.setMaxAge(60 * 60 * 24 * 7); // 1주일
    
        // 쿠키 추가
        response.addCookie(accessTokenCookie);
        response.addCookie(refreshTokenCookie);
    
        response.sendRedirect("/Member/user-info");
  3. 정보를 얻게 됩니다. image
f1v3-dev commented 2 months ago

전체적인 흐름을 알려주실 수 있나요?

클라이언트에서 로그인 요청을 받으면 백엔드 쪽에서 어떻게 하는건지 잘 이해가 안되네요

RTUnu12 commented 2 months ago
  1. 클라이언트에서 api/v1/auth/kakao 등으로 로그인 요청을 합니다.
  2. 302 리다이렉트로 카카오 로그인 창이 떠서 성공 시 MemberAuthService에서 loadUser로 DB에 받은 멤버의 값을 저장합니다.
  3. OAuth2SuccessHandler에 의해 AccessToken과 RefreshToken이 쿠키로 설정되고 이후 **프론트가 아닌 백엔드의 유저를 AccessToken으로 찾는 요청 경로로 리다이렉트됩니다.
  4. 리다이렉트된 요청 경로에선 쿠키 중에 AccessToken을 가져와 토큰을 jwtProvider의 validate로 kakaoId를 뽑아냅니다.
  5. kakaoId로 유저를 찾고 이를 DTO로 리턴합니다.
f1v3-dev commented 2 months ago

클라이언트에게 직접 유저에 대한 정보를 바로 준다는 말인가요?

RTUnu12 commented 2 months ago

그런...셈이긴 해요 DTO로 조정이 가능하긴 한데. 더 좋은 방법이 있을까요? Token을 쿠키로 주지 말고 SecurityContextHolder.getContext().getAuthentication().getName();를 통해 하는방법도 있어요.

f1v3-dev commented 2 months ago

프론트쪽에 회원에 관한 정보보다는 Access Token, Refresh Token을 보내주는게 맞는 것 같아요

현재 API를 호출할 때에도 Filter를 통해서 Header에 값이 있는지 파악하고, ContextHolder에 넣어주는 작업을 해놨는데 이걸 또 변경하게 되면 너무 많은 수정이 필요해보입니다..

Front 쪽으로 Access Token 값을 response body에 담아서 보내거나, 짧은 시간동안 유지되는 Cookie에 담아서 보내는게 가장 좋지 않을까요?

RTUnu12 commented 2 months ago

그것도 좋을것 같긴 하네요. 좀 더 찾아봐야 할듯?

f1v3-dev commented 2 months ago

모임 생성 로직 (로그인 포함)

생각했던 전체 흐름입니다

f1v3-dev commented 2 months ago

로그인 토큰 발급 과정

위의 방식으로 결정