Google OAuth2를 통한 로그인 구현 중, 실제 서비스 적용 시 발생했던 문제들과 그 해결 과정을 정리합니다.
JWT 기반 로그인, 쿠키 전달, 프론트 리다이렉트, OIDC 이슈, Spring Security 예외까지 전부 다뤘습니다.
OAuth2 + JWT 흐름 개요
- 사용자가 Google로 로그인 요청
- OAuth2 인증 성공 시, 사용자 정보 받아옴
- JWT 토큰 발급
- JWT를 쿠키에 저장해서 프론트엔드로 전달
- 프론트는 쿠키를 읽어 로그인 처리
accessToken)http://localhost:3000)Attribute value for 'sub' cannot be null에러 메시지
IllegalArgumentException: Attribute value for 'sub' cannot be null
원인
scope에 openid가 없으면 sub 필드가 제공되지 않음 → 내부적으로 실패
해결
scope: openid, email, profile
ClassCastException: DefaultOidcUser cannot be cast to CustomOAuth2User원인
scope: openid가 설정되면 Spring은 DefaultOidcUser를 사용
→ SuccessHandler에서 CustomOAuth2User로 캐스팅 시도하다 예외 발생
해결instanceof 로 타입 체크 후 분기 처리
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);
/login?error로 리다이렉트됨원인
해결
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 방식을 바꾸기로 하여 다시 작성..