Coding History/Team YesY

구글로그인 검증.

BlackBirdIT 2024. 12. 29. 22:51

이제 프론트에서 버튼 만들었다고 해서 백엔드랑 프론트에서 제대로 통신을 하는가? 에 대한 확인 절차가 필요했음.

그래서 하는데 좀 문제가 많았는데 내가 작성을 하면서 했던게 아니라 지금부터는 작성하면서 진행하려고함.

일단 제일 큰 문제는 이거였음.

  1. 백엔드와 프론트를 완전히 분리했다.
  2. 내가 작성한 시큐리티 설정들은 분리된 환경을 상정하지 않고 작성했다.
  3. 때문에 HTML을 반환하려 하면서 프론트에서 제대로 동작을 하지 않는다.

이게 지금 문제의 결론임.

그래서 성공 처리 핸들러와 실패처리 핸들러 모두 수정했고, 기존 리디렉션 로직을 아예 없앴다.

또, 반환하는 데이터는 오직 JSON만 반환하도록 변경해줬음.

결론은 백엔드 시큐리티 핸들러들이 단순 JSON만 반환한다는거임.

@Slf4j
@Component
public class CustomOAuth2SuccessHandler implements AuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
        CustomOAuth2User customOAuth2User = (CustomOAuth2User) authentication.getPrincipal();

        log.info("JWT 인증 성공: {}, UserId: {}, isNewUser: {}", customOAuth2User.getEmail(), customOAuth2User.getUserId(), customOAuth2User.isNewUser());

        // Access Token 쿠키에 저장
        Cookie accessTokenCookie = new Cookie("accessToken", customOAuth2User.getAccessToken());
        accessTokenCookie.setHttpOnly(true);
        accessTokenCookie.setSecure(true); // HTTPS 환경에서만 동작
        accessTokenCookie.setPath("/");
        accessTokenCookie.setMaxAge(15 * 60); // 15분
        response.addCookie(accessTokenCookie);

        // JSON 응답 반환
        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");
        response.getWriter().write("{\"isNewUser\": " + customOAuth2User.isNewUser() + "}");
    }
}

성공처리 핸들러,

@Component
public class CustomOAuth2FailureHandler implements AuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        // 예외 메시지를 추출
        String errorMessage;

        if (exception instanceof EmailAlreadyExistsException) {
            errorMessage = exception.getMessage();  // 이메일 중복 메시지 사용
        } else {
            errorMessage = "알 수 없는 이유로 로그인에 실패했습니다.";
        }

        // JSON 형식으로 응답
        response.setContentType("application/json");
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 401 상태 코드
        response.getWriter().write(new ObjectMapper().writeValueAsString(Map.of(
                "status", "failure",
                "message", errorMessage
        )));
    }
}

실패처리 핸들러 각각 이렇게 작성해줬음.


그래서 로그인 이후로 오류는 반환하지 않지만 여전히 문제가 있다면 사용자는 이런 화면을 보기 싫을거임.

그래서 이후엔 이제 프론트에서 처리를 해줘야함.

왜? 백엔드에서 하던 리디렉션 로직들이 전부 삭제되었기 때문임.

지금 백엔드 코드에서 전달해주는 값들중 하나인 isNewUser만 나오는게 당연함.. (이건 이후에 필요한 값들을 전달해줘서 프론트에서 재사용하게하면 될 것 같음)

여튼 이제 프론트에서 어떻게 처리할지 고민을 좀 해보자.

소셜로그인이기 때문에 /login/oauth2/code/google에서 응답을 받게될거임.

.......


결론은 일단 성공은 했음.

좀 해맸는데 처음에는 시큐리티에서 해주는 인증 로직을 포기하고 컨트롤러와 서비스로 따로 구현을 하려고 했음. 그러니까 구글에서 주는 엑세스 토큰 등을 받아와서 직접 인증을 하고 프론트로 전달하는 방식을 채용하려고 했는데, 하다보니까 복잡하고 시큐리티가 알아서 처리해주는데 굳이? 라는 생각이 들어서 애초에 다른 좋은 방법이 없을까에 대한 고민을 좀 깊게 함.

  application:
    name: build_develop
    base-url: ${BACKEND_URL}
    frontend:
      url: ${FRONTEND_URL}

이런 야물 설정을 추가해서,

CustomOAuth2SuccessHandler를 다시 사용하게끔 하고,

public class CustomOAuth2SuccessHandler implements AuthenticationSuccessHandler {

    @Value("${spring.application.frontend.url}")
    private String frontendUrl; // 프론트엔드 URL

    @PostConstruct
    public void checkFrontendUrl() {
        System.err.println("Frontend URL during initialization: " + frontendUrl);
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {

        System.err.println("Frontend URL in SuccessHandler: " + frontendUrl);

        CustomOAuth2User customOAuth2User = (CustomOAuth2User) authentication.getPrincipal();

        log.info("JWT 인증 성공: {}, UserId: {}, isNewUser: {}", customOAuth2User.getEmail(), customOAuth2User.getUserId(), customOAuth2User.isNewUser());

        // Access Token 쿠키에 저장
        Cookie accessTokenCookie = new Cookie("accessToken", customOAuth2User.getAccessToken());
        accessTokenCookie.setHttpOnly(true);
        accessTokenCookie.setSecure(true); // HTTPS 환경에서만 동작
        accessTokenCookie.setPath("/");
        accessTokenCookie.setMaxAge(15 * 60); // 15분
        response.addCookie(accessTokenCookie);

        // 프론트엔드로 리디렉션
        String redirectUrl = frontendUrl + "/main";;
        System.err.println("redirectUrl: " + redirectUrl);
        response.sendRedirect(redirectUrl);
    }
}

프론트로 직접 리디렉션하는 간단한 방식으로 구현함.

그럼 프론트로도 JWT 토큰이 제대로 들어오는 것을 볼 수 있고,

HttpOnly 설정이 제대로 되었는가? 도 확인을 해봤는데 출력되지 않는 것을 보니까 제대로 설정됐음.

일단은 여기까지 하자.


앞으로 해야할 일

  1. 로그아웃 처리 (엑세스토큰, Redis의 리프레시 토큰 삭제)
  2. 토큰 만료시간 감지 및 리프레시 토큰으로 엑세스 토큰 발급 구현.
  3. 위의 사항이 다 정상작동 된다면 깃 허브 로그인 검증 및 수정.

백엔드와 프론트의 완전한 분리 상황에서의 시큐리티는 또 처음이라 은근히 많이 해맸는데 그래도 뭐 결국 해내긴 했다.