Spring Boot Google OAuth2 + JWT 로그인 구현 & 트러블슈팅 총정리

kaidey·2025년 4월 10일

Google OAuth2를 통한 로그인 구현 중, 실제 서비스 적용 시 발생했던 문제들과 그 해결 과정을 정리합니다.

JWT 기반 로그인, 쿠키 전달, 프론트 리다이렉트, OIDC 이슈, Spring Security 예외까지 전부 다뤘습니다.


OAuth2 + JWT 흐름 개요

  1. 사용자가 Google로 로그인 요청
  2. OAuth2 인증 성공 시, 사용자 정보 받아옴
  3. JWT 토큰 발급
  4. JWT를 쿠키에 저장해서 프론트엔드로 전달
  5. 프론트는 쿠키를 읽어 로그인 처리

환경 구성 요약

  • Backend: Spring Boot 3.2.x
  • Security: Spring Security 6
  • OAuth2: Google
  • JWT: 직접 발급 (accessToken)
  • Frontend: React (http://localhost:3000)

3. 발생한 주요 오류와 트러블슈팅

🔴 1. Attribute value for 'sub' cannot be null

에러 메시지

IllegalArgumentException: Attribute value for 'sub' cannot be null

원인

scopeopenid가 없으면 sub 필드가 제공되지 않음 → 내부적으로 실패

해결

scope: openid, email, profile

🔴 2. ClassCastException: DefaultOidcUser cannot be cast to CustomOAuth2User

원인

scope: openid가 설정되면 Spring은 DefaultOidcUser를 사용

SuccessHandler에서 CustomOAuth2User로 캐스팅 시도하다 예외 발생

해결instanceof 로 타입 체크 후 분기 처리


🔴 3. JWT 미전달 (JSESSIONID만 존재)

원인

Spring Security 기본은 세션 기반 인증 → JWT를 쿠키로 내려주지 않음

해결

Cookie cookie = new Cookie("ACCESS_TOKEN", token);
cookie.setHttpOnly(true);
cookie.setSecure(false); // 로컬 개발용
cookie.setPath("/");
cookie.setMaxAge(3600);
response.addCookie(cookie);

🔴 4. 로그인 후 /login?error로 리다이렉트됨

원인

  • SuccessHandler 미설정
  • 리다이렉트 URL 누락 또는 오타

해결

response.sendRedirect("http://localhost:3000/");

또는 설정으로 관리:

@Value("${frontend.url}")
private String frontendUrl;

response.sendRedirect(frontendUrl + "/");

##4. 최종 SuccessHandler 코드

@Component
@RequiredArgsConstructor
public class OAuth2SuccessHandler implements AuthenticationSuccessHandler {

    private final JwtUtil jwtUtil;

    @Value("${frontend.url}")
    private String frontendUrl;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
        Object principal = authentication.getPrincipal();
        String token;

        if (principal instanceof CustomOAuth2User customUser) {
            token = customUser.getToken();
        } else if (principal instanceof DefaultOidcUser oidcUser) {
            String email = oidcUser.getAttribute("email");
            token = jwtUtil.generateAccessToken(email, "USER", null);
        } else {
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "지원하지 않는 사용자 타입");
            return;
        }

        Cookie cookie = new Cookie("ACCESS_TOKEN", token);
        cookie.setHttpOnly(true);
        cookie.setSecure(false); // 로컬 개발환경일 경우
        cookie.setPath("/");
        cookie.setMaxAge(60 * 60);
        response.addCookie(cookie);

        response.sendRedirect(frontendUrl + "/");
    }
}

최종적으로는 oauth 방식을 바꾸기로 하여 다시 작성..

profile
백엔드 개발자 꿈나무 GSM 7기 학생

0개의 댓글