안녕하세요. 카카오로그인 관련 문제입니다

문의 시, 사용하시는 개발환경과 디벨로퍼스 앱ID를 알려주세요.


개발환경 : react, spring
앱 id : ID 958550

안녕하세요. 카카오로그인 관련 문의입니다.
로컬에서는 react, spring 로그인이 잘 되는데, 배포 후, 로그인이 안되고 405에러가 뜹니다.

스크린샷 2023-12-04 오후 5.00.02

스크린샷 2023-12-04 오후 5.00.17

import axios from “axios”;
import React, { useEffect } from “react”;
import { useNavigate } from “react-router-dom”;

const KakaoAuth = () => {
const code = new URL(window.location.href).searchParams.get(“code”);
const navigate = useNavigate();

useEffect(() => {
const fetchData = async () => {
try {
const response = await axios.post(
https://smartchart.vercel.app/auth/kakao/callback”,
{ code }
);
console.log(response);
navigate(“/mypage”);
} catch (error) {
console.error(error);
}
};
fetchData();
}, [code]);

return

KakaoAuth
;
};

export default KakaoAuth;


{
“rewrites”: [
{
“source”: “/auth/kakao/callback/:code*”,
“destination”: “https://smartchart.vercel.app/auth/kakao/callback/:code*”
},
{
“source”: “/:path*”,
“destination”: “http://13.125.227.145:8080/:path*”
}
]
}

안녕하세요.

첨부하신 이미지와 같이 카카오와 무관한 개발하신 Redirect URI(“https://smartchart.vercel.app/auth/kakao/callback") 문제입니다.

배포하신 해당 페이지가 정상 작동하는지 확인해주세요.

react에서 code를 spring으로 보내주고, spring에서 로그인 처리 한후, 성공 메세지 react에 보내고, react에서 확인 후, 다른 페이지로 이동하는 로직인데요. redirect uri는 모두 https://smartchart.vercel.app/auth/kakao/callback (react 페이지)로 하는게 맞나요? 동일한 주소요.

카카오로그인(인가코드요청)으로 카카오 측에서 로그인 및 동의창 띄우고 설정하신 리다이렉트 URI로 이동하는데요.
redirect uri는 카카오와 무관하게 인가코드 받아 액세스 토큰 발급하도록 구현된 URL로 이동하면됩니다.
해당 페이지가 어떻게 구현되었는지 알 수 없으므로 리다이렉트 하는게 맞는지 제가 판단할 수 없습니다.


다만, 말씀하신 내용상 이해안가는 부분이

react에서 code를 spring으로 보내주고

해당 spring 백엔드 페이지가 리다이렉트 URI로 추정되고,
로그인 처리 한다는 것은 액세스 토큰 발급 및 사용자 정보조회 API 호출하시는 것으로 이해됩니다.

redirect uri는 모두 https://smartchart.vercel.app/auth/kakao/callback (react 페이지)로 하는게 맞나요?

앞서 설명에는 spring으로 리다이렉트 한다고 하셨으나
위 문의는 react 페이지로 하는게 맞는지 문의하셔서 어떤상황인지 이해하기 어렵습니다.

로그인 해볼 수 있는 주소 있나요?

아래는 spring 코드입니다.

  • react에서 카카오톡으로 받은 code를 spring으로 넘겨주고, spring에서 로그인 처리(카카오 토큰 발급, 사용자 정보 조회)하고, 마지막에 성공했다는 메시지를 react로 보내면 다른 페이지로 이동하는 건데요… 그럼 redirect uri를 spring 주소로 해야하나요?

  • 로그인 해볼 수 있는 주소는 react에서 합니다. → https://smartchart.vercel.app/

@PostMapping(“/auth/kakao/callback”)
public ResponseEntity kakaoCallback(
@RequestBody kakaoRequest request,
HttpServletRequest servletRequest) {

   //
   // @RequestParam("code") String code
    //HttpServletRequest request


    // POST방식으로 key=value 데이터를 요청(카카오쪽으로)
    // <3가지 요청방식>
    //Retrofit2
    //OKHttp
    //RestTemplate

    // 프론트엔드에서 코드를 받지 않고, 백엔드에서 직접 HttpServletRequest를 통해 코드 추출
    //String code = request.getParameter("code");




    String code = request.getCode();


    RestTemplate rt = new RestTemplate();

    //HttpHeader 오브젝트 생성
    HttpHeaders headers = new HttpHeaders();
    headers.add("Content-type","application/x-www-form-urlencoded;charset=utf-8");

    // * 변수를 만들어서 하는게 더 좋음.
    // HttpBody 오브젝트 생성
    MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
    params.add("grant_type","authorization_code");
    params.add("client_id","xxx");
    params.add("redirect_uri","https://smartchart.vercel.app/auth/kakao/callback");
    params.add("code",code);

    

    // 위 headers와 params의 값을 갔고 있는 entity 생성
    // HttpHeader와 HttpBody를 하나의 오브젝트에 담기
    HttpEntity<MultiValueMap<String,String>> kakaoTokenRequest =
            new HttpEntity<>(params,headers);

    // 실제 요청
    // Http 요청하기 - Post방식으로 - response 변수의 응답 받음. // 여기서 에러남...!!!!!
   ResponseEntity<String> response = rt.exchange( // exchange()함수는 HttpEntity를 담게 되어있음 , 그래서 위에 HttpEntity를 만듬
           "https://kauth.kakao.com/oauth/token",
           HttpMethod.POST,
            kakaoTokenRequest, // header 값과 body 값이 들어있음
           String.class // 응답은 String으로 받음
    );

// ResponseEntity response;
//
// try {
// response = rt.exchange(
// “https://kauth.kakao.com/oauth/token”,
// HttpMethod.POST,
// kakaoTokenRequest,
// String.class
// );
//
// // 여기에 정상적인 응답을 처리하는 코드 추가
// // …
//
// } catch (HttpClientErrorException e) {
// // 여기에 예외 처리 코드 추가
// System.out.println("HTTP Status Code: " + e.getRawStatusCode());
// System.out.println("Response Body: " + e.getResponseBodyAsString());
//
// // 추가: 오류 메시지를 로깅
// logger.error("Kakao Token API Error - Status Code: " + e.getRawStatusCode() +
// ", Response Body: " + e.getResponseBodyAsString());
//
// // 원인에 따라 다른 처리를 하거나 예외를 다시 던질 수 있습니다.
// // 예를 들어, 응답 본문에서 오류 메시지를 추출하여 사용자에게 알려줄 수 있습니다.
//
// // response를 null이 아닌 다른 값으로 초기화하여 변수가 null이 되지 않도록 합니다.
// response = new ResponseEntity<>(e.getResponseBodyAsString(), e.getResponseHeaders(), e.getRawStatusCode());
// }

    // 라이브러리 종류 - Gson, Json Simple, ObjectMapper
    // Jason 데이터를 자바에서 처리하기 위해 Object를 바꾼 것.
    ObjectMapper objectMapper = new ObjectMapper();
    OAuthToken oAuthToken = null;

    try {
        oAuthToken = objectMapper.readValue(response.getBody(), OAuthToken.class);
    } catch (JsonProcessingException e) {
        throw new RuntimeException(e);
    }
    logger.info("####################### 카카오 엑세스 토큰 : " + oAuthToken.getAccess_token());


    RestTemplate rt2 = new RestTemplate();


    //HttpHeader 오브젝트 생성
    HttpHeaders headers2 = new HttpHeaders();
    headers2.add("Authorization","Bearer " + oAuthToken.getAccess_token());  // Bearer 뒤에 한칸 띄어놔야함.

    headers2.add("Content-type","application/x-www-form-urlencoded;charset=utf-8");


    // 위 headers와 params의 값을 갔고 있는 entity 생성
    // HttpHeader와 HttpBody를 하나의 오브젝트에 담기
    HttpEntity<MultiValueMap<String,String>> kakaoProfileRequest2 =
            new HttpEntity<>(headers2);

    // 실제 요청
    // Http 요청하기 - Post방식으로 - response 변수의 응답 받음.
    ResponseEntity<String> response2 = rt2.exchange( // exchange()함수는 HttpEntity를 담게 되어있음 , 그래서 위에 HttpEntity를 만듬
            "https://kapi.kakao.com/v2/user/me",
            HttpMethod.POST,
            kakaoProfileRequest2, // header 값이 들어있음
            String.class // 응답은 String으로 받음

    );


    logger.info(response2.getBody());


    ObjectMapper objectMapper2 = new ObjectMapper();
    KakaoProfile kakaoProfile = null;

    try {
        kakaoProfile = objectMapper2.readValue(response2.getBody(), KakaoProfile.class);
    } catch (JsonProcessingException e) {
        throw new RuntimeException(e);
    }

    // Patient 오브젝트 : name, email, password
    logger.info("####################### 카카오 아이디(번호) : " + kakaoProfile.getId());
    logger.info("####################### 카카오 이메일 : " + kakaoProfile.getKakao_account().getEmail());


    // System.out.println("환자 이름 : " + kakaoProfile.getKakao_account().getEmail() + "_" + kakaoProfile.getId()); //   // 예를 들어 유저네임 중복 안되게 하기 위한 tip
   logger.info("####################### 환자 이름 : " + kakaoProfile.getProperties().getNickname());
    logger.info("####################### 환자 이메일 :" + kakaoProfile.getKakao_account().getEmail());
    // UUID란 -> 중복되지 않는 어떤 특정 값을 만들어내는 알고리즘
    //UUID garbagePassword = UUID.randomUUID();  // 임시방편으로 넣으 놓은 비밀번호임. (결국 쓰레기 비밀번호)
    logger.info("####################### 환자서버 패스워드 :" + cosKey);


    // dto 값 넣기
    PatientJoinRequest kakaoRequest = PatientJoinRequest.builder()
            .email(kakaoProfile.getKakao_account().getEmail())
            .password(cosKey)
            .name(kakaoProfile.getProperties().getNickname())
            .gender("null")
            .age(0)
            .phoneNumber(0)
            .role(Role.PATIENT)
            .oauth("kakao")
            .build();

    PatientLoginRequest kakaoLoginRequest = PatientLoginRequest.builder()
            .email(kakaoProfile.getKakao_account().getEmail())
            .password(cosKey.toString())
            .build();


    // 가입자 혹은 비가입자 체크 해서 처리
    logger.info("#######################", kakaoRequest.getEmail());
    Patient originPatient = patientService.회원찾기(kakaoRequest.getEmail());


    // 비가입자일 경우 -> 회원가입
    if(originPatient.getEmail() == null) {
        logger.info("####################### 기존 회원이 아니기에 자동 회원가입을 진행합니다.");
        patientService.register(kakaoRequest);

        // 세션 생성
        HttpSession session = servletRequest.getSession();

        // 세션 만료 시간 설정 (예: 30분)
        session.setMaxInactiveInterval(180 * 60);

        session.setAttribute("patientId", originPatient.getId()); // .get().getId()는 주로 Java의 스트림(Stream)이나 Optional에서 값을 추출하고 해당 객체의 ID 값을 가져오는 용도로 사용되는 코드 패턴
        session.setAttribute("patientEmail", originPatient.getEmail());
        session.setAttribute("patientName", originPatient.getName());
    } else {
        logger.info("####################### 자동 로그인을 진행합니다.");
        // 가입자일 경우 -> 로그인
        patientService.authenticate(kakaoLoginRequest);

        // 세션 생성
        HttpSession session = servletRequest.getSession();

        // 세션 만료 시간 설정 (예: 30분)
        session.setMaxInactiveInterval(180 * 60);

        session.setAttribute("patientId", originPatient.getId()); // .get().getId()는 주로 Java의 스트림(Stream)이나 Optional에서 값을 추출하고 해당 객체의 ID 값을 가져오는 용도로 사용되는 코드 패턴
        session.setAttribute("patientEmail", originPatient.getEmail());
        session.setAttribute("patientName", originPatient.getName());
    }



    // message
    headers.setContentType(new MediaType("application", "json", Charset.forName("UTF-8")));
    Message message = new Message();

    message.setCode(200);
    message.setMessage("카카오톡 로그인 성공");


    return new ResponseEntity<>(message,  HttpStatus.OK);
}

}

" spring에서 로그인 처리(카카오 토큰 발급, 사용자 정보 조회)하고" 이부분 처리하는 URI를 redirect uri 이라 합니다.
redirect uri 에서 처리 후, 페이지 이동은 운영하시는 서비스에서 사용자가 이어서 액션 할 수 있는 페이지로 이동 시켜주시면 됩니다.

958550 앱의 로그를 보니 인가코드 요청만하고 액세스 토큰 발급 이력이 전혀 없습니다.
" spring에서 로그인 처리(카카오 토큰 발급, 사용자 정보 조회)하고" 이부분 처리가 제대로 안되는 것으로 보입니다.

확인 부탁드려요.

958550 앱의 로그를 보니 인가코드 요청만하고 액세스 토큰 발급 이력이 전혀 없습니다.
" spring에서 로그인 처리(카카오 토큰 발급, 사용자 정보 조회)하고" 이부분 처리가 제대로 안되는 것으로 보입니다.

이 부분에서 로컬에서 실행했을 때는 사용자 정보 조회가 가능하고, db에 회원가입도 되고, 정상 작동했는데, 그 이유가 궁금합니다.

개발하신 시스템 종속적 문제라 카카오에서는 디버깅할 수 없으므로 해당 내용 파악할 수 없습니다.

시스템내 로깅하셔서 확인해보시면 좋을 것같습니다.