LUMI_dev

카카오 로그인 (OAuth) #1. 카카오 사용자 정보 가져오기 본문

스파르타 코딩 클럽 | 자바 심화 과정/Spring Master (심화 주차)

카카오 로그인 (OAuth) #1. 카카오 사용자 정보 가져오기

luminous_dev 2025. 2. 7. 12:53

 

 

 

 

 

카카오계정

 

accounts.kakao.com

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

http://localhost:8080/api/user/kakao/callback

 

 

 

 

 

 

 

만약 카카오계정(이메일) 상태가 권한 없음으로 뜨면, 

개인정보 보호를 위해 비즈니스 인증한 서비스에만 동의항목 제공되도록 변경되었으므로 
비즈앱 전환 후, 비즈니스인증 받으시고 개인정보 동의 설정 권한 요청이 필요하다는 것

 

 

비즈니스 > 개인 개발자 비즈 앱 전환 

 

 

 

 

그럼 이제 사용 가능

 

 

 

닉네임 

 

 

이메일 

 

 

login.html

<button id="login-kakao-btn" onclick="location.href='https://kauth.kakao.com/oauth/authorize?client_id={REST_API_KEY}&redirect_uri=http://localhost:8080/api/user/kakao/callback&response_type=code'">
    카카오로 로그인하기
</button>

해당 코드의 { REST_API_KEY }에 우리의 REST_API_KEY를 넣어주기 

 


REST_API_KEY 찾는 방법

 

 

 

카카오 서버에서 보내주는 인가 코드를 처리하는 컨트롤러가 필요 

 

애플리케이션 등록할 때 redirect url 설정해줌 → 이유) 카카오서버에서 redirect url로 인가코드를 보내줘서

인가코드를 받는 컨트롤러를 만들어줘야 함 

 

 

UserController

 

 private final KakaoService kakaoService;
 
 .
 
 .
 
 .

//카카오에서 보내는 인가 코드는 쿼리 스트링 방식으로 넘어오고, @RequestParam으로 받을 수 있음
//완료되면 이 jwt를 생성해서 쿠키를 우리가 직접 만들어서 브라우저에게 시킬 것임
//이전에 회원 기능 구현할 때는 header에 jwt 넣었음
//우리 client 구조 상 카카오 login 했을 때는 우리가 직접 쿠키를 생성해서 jwt에 넣은 다음에 브라우저에 자동으로 set될 수 있도록 구현해보기
@GetMapping("/user/kakao/callback")
public String kakaoLogin(@RequestParam String code, HttpServletResponse response){

}

 

 

 

UserService

package com.sparta.myselectshop.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sparta.myselectshop.dto.KakaoUserInfoDto;
import com.sparta.myselectshop.jwt.JwtUtil;
import com.sparta.myselectshop.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;

@Slf4j(topic = "KAKAO Login")
@Service
@RequiredArgsConstructor
public class KakaoService {

    private final PasswordEncoder passwordEncoder;
    private final UserRepository userRepository;
    private final RestTemplate restTemplate; //빨간 줄 : RestTemplate을 그냥 Builder로 만들 수 있다 
        //빨간줄 뜨는 이유 : restTemplate이 bean이 아닌 Rest Template Builder을 통해서 생성할 수 있도록 유도


    //Bean을 수동으로 등록해서 관리하는 방법
    
    private final JwtUtil jwtUtil;

    public String kakaoLogin(String code) throws JsonProcessingException {
        // 1. "인가 코드"로 "액세스 토큰" 요청
        String accessToken = getToken(code);

        // 2. 토큰으로 카카오 API 호출 : "액세스 토큰"으로 "카카오 사용자 정보" 가져오기
        KakaoUserInfoDto kakaoUserInfo = getKakaoUserInfo(accessToken);

        return null;
    }



}

 

bean을 수동으로 등록해줘야함 > RestTemplateConfig 추가 

 

 

//주의 :  버전 3.4.0 이상에서 지원 중단되며 제거될 예정인 메서드 있음 

package com.sparta.myselectshop.config;

import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

import java.time.Duration;

@Configuration
public class RestTemplateConfig  {
    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
        return restTemplateBuilder
                // RestTemplate 으로 외부 API 호출 시 일정 시간이 지나도 응답이 없을 때
                // 무한 대기 상태 방지를 위해 강제 종료 설정
                .setConnectTimeout(Duration.ofSeconds(5)) // 5초
                .setReadTimeout(Duration.ofSeconds(5)) // 5초
                .build();
    }
}

 

다시 KakaoService

아래 액세스 토큰 요청 코드 하단에 넣어주기 

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", "본인의 REST API키");
    body.add("redirect_uri", "http://localhost:8080/api/user/kakao/callback");
    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();
}

 

지금 카카오 사용자 정보 가져오기 중 인증 코드로 토큰 요청 수행 중 

 

 

KakaoService

package com.sparta.myselectshop.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sparta.myselectshop.dto.KakaoUserInfoDto;
import com.sparta.myselectshop.jwt.JwtUtil;
import com.sparta.myselectshop.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;

@Slf4j(topic = "KAKAO Login")
@Service
@RequiredArgsConstructor
public class KakaoService {

    private final PasswordEncoder passwordEncoder;
    private final UserRepository userRepository;
    private final RestTemplate restTemplate; //빨간 줄 : RestTemplate을 그냥 Builder로 만들 수 있다 
        //빨간줄 뜨는 이유 : restTemplate이 bean이 아닌 Rest Template Builder을 통해서 생성할 수 있도록 유도


    //Bean을 수동으로 등록해서 관리하는 방법

    private final JwtUtil jwtUtil;

    public String kakaoLogin(String code) throws JsonProcessingException {
        // 1. "인가 코드"로 "액세스 토큰" 요청
        String accessToken = getToken(code);

        // 2. 토큰으로 카카오 API 호출 : "액세스 토큰"으로 "카카오 사용자 정보" 가져오기
        KakaoUserInfoDto kakaoUserInfo = getKakaoUserInfo(accessToken);

        return null;
    }

    
    //인증 코드로 토큰 요청
    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", "본인의 REST API키");
        body.add("redirect_uri", "http://localhost:8080/api/user/kakao/callback");
        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();
    }



}

 

이중 본인의 REST API 키에 다시 RESTAPI 넣어주기 

// HTTP Body 생성
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
body.add("grant_type", "authorization_code");
body.add("client_id", "본인의 REST API키"); //여기에 RESTAPI 코드 다시 넣어주기 
body.add("redirect_uri", "http://localhost:8080/api/user/kakao/callback");
body.add("code", code);

 

 

사용자 정보 요청 KakaoService 하단에 추가 

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);
}

 

 

전체 KakaoService코드 

package com.sparta.myselectshop.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sparta.myselectshop.dto.KakaoUserInfoDto;
import com.sparta.myselectshop.jwt.JwtUtil;
import com.sparta.myselectshop.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;

@Slf4j(topic = "KAKAO Login")
@Service
@RequiredArgsConstructor
public class KakaoService {

    private final PasswordEncoder passwordEncoder;
    private final UserRepository userRepository;
    private final RestTemplate restTemplate; //빨간 줄 : RestTemplate을 그냥 Builder로 만들 수 있다 
        //빨간줄 뜨는 이유 : restTemplate이 bean이 아닌 Rest Template Builder을 통해서 생성할 수 있도록 유도


    //Bean을 수동으로 등록해서 관리하는 방법

    private final JwtUtil jwtUtil;

    public String kakaoLogin(String code) throws JsonProcessingException {
        // 1. "인가 코드"로 "액세스 토큰" 요청
        String accessToken = getToken(code);

        // 2. 토큰으로 카카오 API 호출 : "액세스 토큰"으로 "카카오 사용자 정보" 가져오기
        KakaoUserInfoDto kakaoUserInfo = getKakaoUserInfo(accessToken);

        return null;
    }

    
    //kakaoLogin에서 받은 인증 코드로 토큰 요청
    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();
        //kakao Developers에서 요청한대로 http 형태를 만들어서 Rest template으로 API를 요청함
        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", "13f0c1acd35319458b5893b3dbba382d"); //여기에 RESTAPI 코드 다시 넣어주기
        body.add("redirect_uri", "http://localhost:8080/api/user/kakao/callback");
        body.add("code", code); //받은 인가코드 넣기

        RequestEntity<MultiValueMap<String, String>> requestEntity = RequestEntity
                .post(uri) //body가 있으니까 POST
                .headers(headers)
                .body(body);

        // HTTP 요청 보내기
        ResponseEntity<String> response = restTemplate.exchange( //http 요청이 카카오 서버 쪽으로 감
                requestEntity,
                String.class
        ); //ResponseEntity<String>에서 반환되는 String이 토큰의 형태로 되어 있음 > 그 토큰을 한번 파싱할 것임 (.readTree(response.getBody()))

        // HTTP 응답 (JSON) -> 액세스 토큰 파싱 = 우리가 딱 원하는 액세스 토큰만 딱 뽑아옴 
        JsonNode jsonNode = new ObjectMapper().readTree(response.getBody());
        return jsonNode.get("access_token").asText(); 
        //뽑으면 .asText()니까 String값 반환
        //맨 위의 String accessToken = getToken(code);의 String accessToken
    }




        //사용자 정보 요청 (getKakaoUserInfo 메서드)
        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) //우리가 url 만든 거 보내주기
                    .headers(headers)
                    .body(new LinkedMultiValueMap<>()); //body는 따로 보내줄 필요가 없어서 new~

            // HTTP 요청 보내기
            ResponseEntity<String> response = restTemplate.exchange(
                    requestEntity,
                    String.class //String 타입으로 받아옴
            );

            //Long 타입으로 해당하는 id 받아오는 방법
            JsonNode jsonNode = new ObjectMapper().readTree(response.getBody());

            //해댱하는 id값들을 받아올 수 있음
            Long id = jsonNode.get("id").asLong();


            //properties 중에서 nickname 값을 가져옴
            String nickname = jsonNode.get("properties")
                    .get("nickname").asText();

            //properties 중에서 email 값을 가져옴
            String email = jsonNode.get("kakao_account")
                    .get("email").asText();

            log.info("카카오 사용자 정보: " + id + ", " + nickname + ", " + email);
            
            //KakaoUserInfoDto에 값을 넣어줌
            return new KakaoUserInfoDto(id, nickname, email);
        }

}

 

kakaoService 중

//Long 타입으로 해당하는 id 받아오는 방법
JsonNode jsonNode = new ObjectMapper().readTree(response.getBody());

//해댱하는 id값들을 받아올 수 있음
Long id = jsonNode.get("id").asLong();


//properties 중에서 nickname 값을 가져옴
String nickname = jsonNode.get("properties")
        .get("nickname").asText();

//properties 중에서 email 값을 가져옴
String email = jsonNode.get("kakao_account")
        .get("email").asText();

 

카카오 사용자 정보 JSON 의 예

{
  "id": 1632335751,
  "properties": {
    "nickname": "르탄이",
    "profile_image": "http://k.kakaocdn.net/...jpg",
    "thumbnail_image": "http://k.kakaocdn.net/...jpg"
  },
  "kakao_account": {
    "profile_needs_agreement": false,
    "profile": {
      "nickname": "르탄이",
      "thumbnail_image_url": "http://k.kakaocdn.net/...jpg",
      "profile_image_url": "http://k.kakaocdn.net/...jpg"
    },
    "has_email": true,
    "email_needs_agreement": false,
    "is_email_valid": true,
    "is_email_verified": true,
    "email": "letan@sparta.com"
  }
}

원하는 정보가 르탄이 (value)라고 하면 properties > nickname > 으로 들어가서 얻어와야 함

id는 바로 빠져나와있어서 그냥 가져옴 

email은 kakao_account 한번 들어가고 > email 

 


인가 코드를 받아오기 위한 컨트롤러

UserController

package com.sparta.myselectshop.controller;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.sparta.myselectshop.dto.SignupRequestDto;
import com.sparta.myselectshop.dto.UserInfoDto;
import com.sparta.myselectshop.entity.UserRoleEnum;
import com.sparta.myselectshop.jwt.JwtUtil;
import com.sparta.myselectshop.security.UserDetailsImpl;
import com.sparta.myselectshop.service.FolderService;
import com.sparta.myselectshop.service.KakaoService;
import com.sparta.myselectshop.service.UserService;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@Slf4j
@Controller
@RequiredArgsConstructor
@RequestMapping("/api")
public class UserController {

    private final UserService userService;
    private final FolderService folderService;
    private final KakaoService kakaoService;

    @GetMapping("/user/login-page")
    public String loginPage() {
        return "login";
    }

    @GetMapping("/user/signup")
    public String signupPage() {
        return "signup";
    }

    @PostMapping("/user/signup")
    public String signup(@Valid SignupRequestDto requestDto, BindingResult bindingResult) {
        // Validation 예외처리
        List<FieldError> fieldErrors = bindingResult.getFieldErrors();
        if (fieldErrors.size() > 0) {
            for (FieldError fieldError : bindingResult.getFieldErrors()) {
                log.error(fieldError.getField() + " 필드 : " + fieldError.getDefaultMessage());
            }
            return "redirect:/api/user/signup";
        }

        userService.signup(requestDto);

        return "redirect:/api/user/login-page";
    }

    // 회원 관련 정보 받기
    @GetMapping("/user-info")
    @ResponseBody
    public UserInfoDto getUserInfo(@AuthenticationPrincipal UserDetailsImpl userDetails) {
        String username = userDetails.getUser().getUsername();
        UserRoleEnum role = userDetails.getUser().getRole();
        boolean isAdmin = (role == UserRoleEnum.ADMIN);

        return new UserInfoDto(username, isAdmin);
    }

    @GetMapping("/user-folder")
    public String getUserInfo(Model model, @AuthenticationPrincipal UserDetailsImpl userDetails) {

        model.addAttribute("folders", folderService.getFolders(userDetails.getUser()));
        return "index :: #fragment";
    }

    //카카오에서 보내는 인가 코드는 쿼리 스트링 방식으로 넘어오고, @RequestParam으로 받을 수 있음
    //완료되면 이 jwt를 생성해서 쿠키를 우리가 직접 만들어서 브라우저에게 시킬 것임
    //이전에 회원 기능 구현할 때는 header에 jwt 넣었음
    //우리 client 구조 상 카카오 login 했을 때는 우리가 직접 쿠키를 생성해서 jwt에 넣은 다음에 브라우저에 자동으로 set될 수 있도록 구현해보기
    @GetMapping("/user/kakao/callback")
    public String kakaoLogin(@RequestParam String code, HttpServletResponse response) throws JsonProcessingException {
        //kakaoService 단의 kakoLogin 메소드
        String token = kakaoService.kakaoLogin(code); //String token = jwt token
        //빨간 줄 : 예외를 시그니처에 추가 누르기
        //kakao에서 받아온 token을 쿠키에 넣어주고 reponse 객체 넣어주는 작업
        Cookie cookie = new Cookie(JwtUtil.AUTHORIZATION_HEADER, token);
        cookie.setPath("/");
        response.addCookie(cookie); //브라우저의 jwt 값이 set 될 것임


        return "redirect:/";

    }

}

 

KakaoService

1. 인가코드로 액세스 토큰 요청하는 getToken 메소드

2. getToken으로 전달 받아온 액세스 토큰을 다시 한번 카카오 서버에 요청을 해서 그 사용자의 정보를 받아온 다음에 

그 사용자 정보에 우리가 원하는  필요한 데이터만 뽑아옴 (id,nickname, email)

package com.sparta.myselectshop.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sparta.myselectshop.dto.KakaoUserInfoDto;
import com.sparta.myselectshop.jwt.JwtUtil;
import com.sparta.myselectshop.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;

@Slf4j(topic = "KAKAO Login")
@Service
@RequiredArgsConstructor
public class KakaoService {

    private final PasswordEncoder passwordEncoder;
    private final UserRepository userRepository;
    private final RestTemplate restTemplate; //빨간 줄 : RestTemplate을 그냥 Builder로 만들 수 있다 
        //빨간줄 뜨는 이유 : restTemplate이 bean이 아닌 Rest Template Builder을 통해서 생성할 수 있도록 유도


    //Bean을 수동으로 등록해서 관리하는 방법

    private final JwtUtil jwtUtil;

    public String kakaoLogin(String code) throws JsonProcessingException {
        // 1. "인가 코드"로 "액세스 토큰" 요청
        String accessToken = getToken(code);

        // 2. 토큰으로 카카오 API 호출 : "액세스 토큰"으로 "카카오 사용자 정보" 가져오기
        KakaoUserInfoDto kakaoUserInfo = getKakaoUserInfo(accessToken);

        return null;
    }

    
    //kakaoLogin에서 받은 인증 코드로 토큰 요청
    private String getToken(String code) throws JsonProcessingException {

        log.info("인가코드 : "+code);

        // 요청 URL 만들기
        URI uri = UriComponentsBuilder
                .fromUriString("https://kauth.kakao.com") //여긴 정해진 주소를 넣기
                .path("/oauth/token")
                .encode()
                .build()
                .toUri();

        // HTTP Header 생성
        HttpHeaders headers = new HttpHeaders();
        //kakao Developers에서 요청한대로 http 형태를 만들어서 Rest template으로 API를 요청함
        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", "13f0c1acd35319458b5893b3dbba382d"); //여기에 RESTAPI 코드 다시 넣어주기
        body.add("redirect_uri", "http://localhost:8080/api/user/kakao/callback");
        body.add("code", code); //받은 인가코드 넣기

        RequestEntity<MultiValueMap<String, String>> requestEntity = RequestEntity
                .post(uri) //body가 있으니까 POST
                .headers(headers)
                .body(body);

        // HTTP 요청 보내기
        ResponseEntity<String> response = restTemplate.exchange( //http 요청이 카카오 서버 쪽으로 감
                requestEntity,
                String.class
        ); //ResponseEntity<String>에서 반환되는 String이 토큰의 형태로 되어 있음 > 그 토큰을 한번 파싱할 것임 (.readTree(response.getBody()))

        // HTTP 응답 (JSON) -> 액세스 토큰 파싱 = 우리가 딱 원하는 액세스 토큰만 딱 뽑아옴 
        JsonNode jsonNode = new ObjectMapper().readTree(response.getBody());
        return jsonNode.get("access_token").asText(); 
        //뽑으면 .asText()니까 String값 반환
        //맨 위의 String accessToken = getToken(code);의 String accessToken
    }




        //사용자 정보 요청 (getKakaoUserInfo 메서드)
        private KakaoUserInfoDto getKakaoUserInfo(String accessToken) throws JsonProcessingException {
            log.info("accessToken : "+accessToken);
        // 요청 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) //우리가 url 만든 거 보내주기
                    .headers(headers)
                    .body(new LinkedMultiValueMap<>()); //body는 따로 보내줄 필요가 없어서 new~

            // HTTP 요청 보내기
            ResponseEntity<String> response = restTemplate.exchange(
                    requestEntity,
                    String.class //String 타입으로 받아옴
            );

            //Long 타입으로 해당하는 id 받아오는 방법
            JsonNode jsonNode = new ObjectMapper().readTree(response.getBody());

            //해댱하는 id값들을 받아올 수 있음
            Long id = jsonNode.get("id").asLong();


            //properties 중에서 nickname 값을 가져옴
            String nickname = jsonNode.get("properties")
                    .get("nickname").asText();

            //properties 중에서 email 값을 가져옴
            String email = jsonNode.get("kakao_account")
                    .get("email").asText();

            log.info("카카오 사용자 정보: " + id + ", " + nickname + ", " + email);
            
            //KakaoUserInfoDto에 값을 넣어줌
            return new KakaoUserInfoDto(id, nickname, email);
        }

}