KOE320 에러 도저히 해결이 안됩니다. 제발 알려주세요 ㅜㅜ

안녕하세요. 컴퓨터 공학과 복수전공을 하며 힘겹게 과제를 하는 대학생입니다.
저는 카카오 로그인을 활용해 기존에 제가 만든 일반적인 회원가입 및 로그인 방식과 합쳐 데이터베이스에 저장하는 부분을 구현하고 있습니다.
지식이 많이 부족하고, 질문을 하는 방법조차 잘 몰라 두서없이 글을 남깁니다.

ID : 1132097

Failed to get access token, response code: 400, response: {“error”:“invalid_grant”,“error_description”:“authorization code not found for code=8yTFsxkVbgaXe2LzFdKyQqJsQCtI6iijAAAAAQo9dGkAAAGSCwhcALG7d-HwzTGR”,“error_code”:“KOE320”}

우선 인텔리제이에서 뜨는 오류는 이렇습니다.
찾아보니 인가 코드 재사용 으로 인한 문제라더군요.
하지만 저는 도저히 재사용된 부분을 찾을 수가 없습니다…
이유를 모르겠어요.
조언 부탁드립니다.

package com.example.project.controller;

import com.example.project.service.OAuthService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import lombok.RequiredArgsConstructor;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@RestController
@RequiredArgsConstructor
@RequestMapping(“/oauth”)
public class OAuthController {

private final OAuthService oAuthService;

// 인가 코드 재사용 방지를 위한 메모리 저장소 (실제 구현에서는 DB나 Redis 사용 추천)
private final Map<String, Boolean> usedAuthorizationCodes = new ConcurrentHashMap<>();

@ResponseBody
@GetMapping("/kakao")
public synchronized ResponseEntity<Map<String, String>> kakaoCallback(@RequestParam String code) {
    Map<String, String> response = new HashMap<>();

    // 인가 코드 재사용 방지 처리
    if (usedAuthorizationCodes.containsKey(code)) {
        response.put("error", "Authorization code has already been used");
        return ResponseEntity.status(400).body(response);
    }

    try {
        // 인가 코드를 사용한 것으로 기록
        usedAuthorizationCodes.put(code, true);

        // 인가 코드를 이용해 액세스 토큰을 가져옴
        String accessToken = oAuthService.getKakaoAccessToken(code);

        // 액세스 토큰이 잘 가져왔는지 확인
        System.out.println("Access Token: " + accessToken);

        // JSON 응답 생성
        response.put("access_token", accessToken);
        return ResponseEntity.ok(response);

    } catch (RuntimeException e) {
        // 인가 코드 사용 실패 시 기록을 삭제하여 다음 시도를 가능하게 함
        usedAuthorizationCodes.remove(code);

        if (e.getMessage().contains("KOE320")) {
            response.put("error", "Authorization code has already been used or is invalid");
            return ResponseEntity.status(400).body(response);
        } else {
            // 일반적인 오류 처리
            response.put("error", "Failed to retrieve access token");
            return ResponseEntity.status(500).body(response);
        }
    }
}

}

package com.example.project.service;

import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;

@Service
public class OAuthService {

private static final Logger logger = LoggerFactory.getLogger(OAuthService.class);

@Value("${kakao.client-id}")
private String clientId;

@Value("${kakao.redirect-uri}")
private String redirectUri;

public String getKakaoAccessToken(String code) {
    String access_Token = "";
    String reqURL = "https://kauth.kakao.com/oauth/token";

    try {
        URL url = new URL(reqURL);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();

        // POST 요청 설정
        conn.setRequestMethod("POST");
        conn.setDoOutput(true);

        // 요청 파라미터 설정
        try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(conn.getOutputStream()))) {
            StringBuilder sb = new StringBuilder();
            sb.append("grant_type=authorization_code");
            sb.append("&client_id=").append(clientId);
            sb.append("&redirect_uri=").append(redirectUri);
            sb.append("&code=").append(code);
            bw.write(sb.toString());
            bw.flush();
        }

        // 응답 코드 확인
        int responseCode = conn.getResponseCode();
        if (responseCode != 200) {
            String errorResponse = "";
            try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getErrorStream()))) {
                String line;
                while ((line = br.readLine()) != null) {
                    errorResponse += line;
                }
            }
            logger.error("Failed to get access token, response code: {}, response: {}", responseCode, errorResponse);
            throw new RuntimeException("Failed to get access token: " + errorResponse);
        }

        // JSON 타입의 응답 메시지 읽기
        StringBuilder result = new StringBuilder();
        try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()))) {
            String line;
            while ((line = br.readLine()) != null) {
                result.append(line);
            }
        }

        // JSON 파싱
        JsonElement element = JsonParser.parseString(result.toString());
        access_Token = element.getAsJsonObject().get("access_token").getAsString();

    } catch (IOException e) {
        logger.error("Exception occurred while getting access token", e);
        throw new RuntimeException("Failed to get access token", e);
    }

    return access_Token;
}

}

아래는 안스 주요 코드입니다.
private void performKakaoLogin() {
if (isKakaoLoginInProgress) return; // 중복 클릭 방지
isKakaoLoginInProgress = true;

    UserApiClient.getInstance().loginWithKakaoAccount(Login.this, (token, error) -> {
        isKakaoLoginInProgress = false;

        if (error != null) {
            Toast.makeText(Login.this, "카카오 로그인 실패: " + error.getMessage(), Toast.LENGTH_SHORT).show();
            Log.e("KakaoLogin", "카카오 로그인 실패", error);
        } else if (token != null) {
            Toast.makeText(Login.this, "카카오 로그인 성공", Toast.LENGTH_SHORT).show();
            Log.i("KakaoLogin", "카카오 로그인 성공! Access Token: " + token.getAccessToken());

            // 서버로 Access Token을 보내기
            sendAuthorizationCodeToServer(token.getAccessToken());
        }
        return null; // 반환값 명시
    });
}

private void sendAuthorizationCodeToServer(String authCode) {
    OAuthApi oAuthApi = ApiClient.getOAuthApiService(this);
    Call<String> call = oAuthApi.getKakaoAccessToken(authCode);

    call.enqueue(new Callback<String>() {
        @Override
        public void onResponse(Call<String> call, Response<String> response) {
            if (response.isSuccessful() && response.body() != null) {
                String accessToken = response.body();
                Log.i("KakaoLogin", "서버로부터 받은 Access Token: " + accessToken);

                // Access Token을 저장하고 필요한 후속 작업 수행
saveAccessToken(accessToken);
                    navigateToMainPage();
                } else {
                    Toast.makeText(Login.this, "서버로부터 Access Token을 받지 못했습니다.", Toast.LENGTH_SHORT).show();
                    Log.e("KakaoLogin", "서버 응답 실패: " + response.code());
                }
            }

            @Override
            public void onFailure(Call<String> call, Throwable t) {
                Toast.makeText(Login.this, "서버 요청 실패", Toast.LENGTH_SHORT).show();
                Log.e("KakaoLogin", "서버 요청 실패", t);
            }
        });
    }

    private void saveAccessToken(String accessToken) {
        SharedPreferences sharedPreferences = getSharedPreferences("MyAppPrefs", MODE_PRIVATE);
        SharedPreferences.Editor editor = sharedPreferences.edit();
        editor.putString("access_token", accessToken);
        editor.apply();
    }

안녕하세요.

확인을 위해 앱 ID 부탁드립니다.


앱ID
https://developers.kakao.com/ 의 내 애플리케이션>앱 설정>요약 정보 : 기본정보에 있는 앱 ID
숫자로된 ID 입니다
ex) 123456

안녕하세요.

(1) 일반적으로 위와 같이 인가코드가 잘 표기되었는데 KOE320에러가 발생하면, 리다이렉트 URI에서 새로고침해서 그렇게됩니다.

KOE320 (An authorization code must be supplied, authorization code not found) 에러가 발생할 때

(2) 인가코드요청과 액세스 토큰 발급요청 시, 리다이렉트 URI를 동일하게 설정해야하는데 아래와 같이 다르게 설정해서 에러 발생하고 있습니다.

인가코드: null
접근토큰: http://127.0.0.1:9000/oauth/kakao

인가코드: http://localhost:8080/oauth/kakao
접근토큰: http://localhost:9000/oauth/kakao

두가지 검토 해보시면 좋을 것 같습니다.