카카오 로그인 500에러 발생

ID 870832


resource: the server responded with a status of 500 () 에러가 발생합니다.
카카오 로그인 중입니다.라는 메시지가 뜨고 그 이후로는 동작을 하지 않습니다.

안녕하세요.

아래 내용 점검 부탁드립니다.

KOE303 :
인가코드와 접근토큰 발급 시 사용되는 redirect_uri 파라미터는 서로 같아야만 합니다.
접근 토큰 발급 시 사용된 redirect_uri 파라미터가 앞서 인가코드 발급에 사용된 값과 달라 KOE303오류가 발생하였습니다.

인가코드: https://gallae-fe.vercel.app/kakao/callback
접근토큰: https://gallae-fe.vercel.app/api/users/kakao/callback

인가코드: null
접근토큰: https://gallae-fe.vercel.app/api/users/kakao/callback

인가코드: http://localhost:5173/kakao/callback
접근토큰: https://gallae-fe.vercel.app/api/users/kakao/callback

인가코드: https://gallae-trip.com/api/users/kakao/callback
접근토큰: https://gallae-fe.vercel.app/api/users/kakao/callback

KOE320 :
인가코드는 성공 실패 여부와 상관없이 단 한번만 사용 가능합니다.
접근 토큰 발급 시, 오류가 발생하였다면 인가코드를 새로 받아 사용 부탁드립니다.

KOE006 :
redirect_uri 는 등록된 URI만 사용할 수 있습니다.
아래 등록되지 않고 사용된 파라미터 값을 확인 및 등록 부탁드립니다.

http://gallae-fe.vercel.app/api/user/kakao/callback
http://gallae-fe.vercel.app/api/users/kakao/callback
http://gallae-trip.com/api/user/kakao/callback
http://gallae-trip.com/api/users/kakao/callback
http://localhost:5173/api/users/kakao/callback
http://localhost:5173/kakaoCallback
http://localhost:5173/users/kakao
https://gallae-fe.vercel.app/api/user/kakao/callback
https://gallae-trip.com/api/user/kakao/callback

모든 코드를 고쳤습니다.
일반 유저가 있는 상태에서 카카오 로그인 시도시 kakaoId가 업데이트 되서 카카오 로그인을 사용할 수 있습니다.
하지만 신규가입을 카카오로그인으로 하는경우 동작하지 않고 있어 문의 남깁니다. 같은 500 에러입니다.

어떤 현상인지 잘 이해하지 못하였는데요, 좀더 설명 부탁드립니다.

그리고, 서비스에서 동일 인가코드를 2회 이상 사용하여 여전히 KOE320 오류가 발생하고 있습니다.
접근토큰 발급 로직을 확인 부탁드립니다.

일반회원으로 가입한 사용자가 카카오 로그인을 시도하면 kakaoId가 추가되며 로그인이 정상적으로 동작합니다. 하지만 카카오로그인으로 신규가입을 시도하면 Error 500과 함께 "카카오 로그인 중입니다"라는 메시지가 화면에 나타나고 멈춥니다.
@Slf4j(topic = “Kakao Login”)
@Service
@RequiredArgsConstructor
public class KakaoService {

private final PasswordEncoder passwordEncoder;
private final UserRepository userRepository;
private final RestTemplate restTemplate;
private final JwtUtil jwtUtil;

@Value("${kakaoClientId}")
private String kakaoClientId;
@Value("${kakaoRedirectUri}")
private String kakaoRedirectUri;

public TokenDto kakaoLogin(String code, HttpServletResponse response) throws JsonProcessingException {
    // 1. "인가 코드"로 "액세스 토큰" 요청
    String accessAuthorizationToken = getToken(code);
    // 2. 토큰으로 카카오 API 호출 : "액세스 토큰"으로 "카카오 사용자 정보" 가져오기
    KakaoUserInfoDto kakaoUserInfo = getKakaoUserInfo(accessAuthorizationToken);
    // 3. 필요시에 회원가입
    Users kakaoUser = registerKakaoUserIfNeeded(kakaoUserInfo);
    // 4. JWT 토큰 반환
    String accessToken = jwtUtil.createAccessToken(kakaoUser.getEmail(), kakaoUser.getUserRole());

    response.setHeader(JwtUtil.ACCESS_KEY, accessToken);

    return new TokenDto(accessToken);
}

private String getToken(String code) throws JsonProcessingException {
    // 요청 URL 만들기
    URI uri = UriComponentsBuilder
            .fromUriString("https://kauth.kakao.com")
            .path("/oauth/token")
            .encode()
            .build()
            .toUri();

    // HTTP Header 생성
    HttpHeaders headers = new HttpHeaders();
    headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");

    // HTTP Body 생성
    MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
    body.add("grant_type", "authorization_code");
    body.add("client_id", kakaoClientId);
    body.add("redirect_uri", kakaoRedirectUri);
    body.add("code", code);

    RequestEntity<MultiValueMap<String, String>> requestEntity = RequestEntity
            .post(uri)
            .headers(headers)
            .body(body);

    // HTTP 요청 보내기
    ResponseEntity<String> response = restTemplate.exchange(
            requestEntity,
            String.class
    );

    // HTTP 응답 (JSON) -> 액세스 토큰 파싱
    JsonNode jsonNode = new ObjectMapper().readTree(response.getBody());
    return jsonNode.get("access_token").asText();
}

private KakaoUserInfoDto getKakaoUserInfo(String accessToken) throws JsonProcessingException {
    // 요청 URL 만들기
    URI uri = UriComponentsBuilder
            .fromUriString("https://kapi.kakao.com")
            .path("/v2/user/me")
            .encode()
            .build()
            .toUri();

    // HTTP Header 생성
    HttpHeaders headers = new HttpHeaders();
    headers.add("Authorization", "Bearer " + accessToken);
    headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");

    RequestEntity<MultiValueMap<String, String>> requestEntity = RequestEntity
            .post(uri)
            .headers(headers)
            .body(new LinkedMultiValueMap<>());

    // HTTP 요청 보내기
    ResponseEntity<String> response = restTemplate.exchange(
            requestEntity,
            String.class
    );

    JsonNode jsonNode = new ObjectMapper().readTree(response.getBody());
    Long id = jsonNode.get("id").asLong();
    String nickname = jsonNode.get("properties")
            .get("nickname").asText();
    String email = jsonNode.get("kakao_account")
            .get("email").asText();

    log.info("카카오 사용자 정보: " + id + ", " + nickname + ", " + email);
    return new KakaoUserInfoDto(id, nickname, email);
}

private Users registerKakaoUserIfNeeded(KakaoUserInfoDto kakaoUserInfo) {
    Long kakaoId = kakaoUserInfo.getId();
    Users kakaoUser = userRepository.findByKakaoId(kakaoId).orElse(null);
    if (kakaoUser == null) {
        String nickname = kakaoUserInfo.getNickname();
        String password = UUID.randomUUID().toString();
        String encodedPassword = passwordEncoder.encode(password);
        String email = kakaoUserInfo.getEmail();

        if (email != null) {
            Users sameEmailUser = userRepository.findByEmail(email).orElse(null);
            // 일반 가입이 되어있는 경우
            if (sameEmailUser != null) {
                kakaoUser = sameEmailUser;
                kakaoUser = kakaoUser.kakaoIdUpdate(kakaoId);
            } else {
                // 신규 회원
                kakaoUser = new Users(email, nickname, encodedPassword, UserRoleEnum.USER);
                kakaoUser = kakaoUser.kakaoIdUpdate(kakaoId);
                userRepository.save(kakaoUser);
            }
        }
    }
    return kakaoUser;
}

}
백엔드 코드입니다

안녕하세요.

500응 답 시, request body 와 response body 내용 확인 부탁드립니다.
인가코드 요청 시, 사용된 client_id와 접근토큰 발급 시 사용되는 client_id가 다른것 같습니다.