Everyday Dev System

카카오 서버에서 인증 코드 가져오기 및 토큰 생성 및 사용자 정보 가져오기 본문

내배캠 주요 학습/Spring 심화

카카오 서버에서 인증 코드 가져오기 및 토큰 생성 및 사용자 정보 가져오기

chaeyoung- 2023. 7. 10. 12:57
애플리케이션은 카카오 서버에서 보내주는 인증 코드를 받아야 합니다.

리다이렉트 url 쪽으로 카카오 서버에서 인증 코드를 보내줍니다. 
그러므로, 인증 코드를 받는 Controller를 만들어 줘야 합니다.

컨트롤러는 받은 인증 코드를 Service 단으로 넘겨줍니다.
Service 단에서는 인증 코드로 토큰을 요청하고 카카오 서버로 부터 토큰을 전달 받습니다.

카카오 API 문서를 보면 아래와 같이 필수 입력 사항들이 나오니, 이를 참고하여야 합니다.
참고 : https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#req-user-info-request

 

 

login.html 내에 들어가는 REST API 키가 필요합니다.

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <meta name="viewport"
        content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <link rel="preconnect" href="https://fonts.gstatic.com">
  <link rel="stylesheet" type="text/css" href="/css/style.css">
  <script src="https://code.jquery.com/jquery-3.7.0.min.js"
          integrity="sha256-2Pmvv0kuTBOenSvLm6bvfBSSHrUJ+3A7x6P5Ebd07/g=" crossorigin="anonymous"></script>
  <script src="https://cdn.jsdelivr.net/npm/js-cookie@3.0.5/dist/js.cookie.min.js"></script>
  <meta charset="UTF-8">
  <title>로그인 페이지</title>
</head>
<body>
<div id="login-form">
  <div id="login-title">Log into Select Shop</div>
  <button id="login-kakao-btn" onclick="location.href='https://kauth.kakao.com/oauth/authorize?client_id={REST_API_KEY}&redirect_uri=http://localhost:8081/api/user/kakao/callback&response_type=code'">
    카카오로 로그인하기
  </button>
  <button id="login-id-btn" onclick="location.href='/api/user/signup'">
    회원 가입하기
  </button>
  <div>
    <div class="login-id-label">아이디</div>
    <input type="text" name="username" id="username" class="login-input-box">

    <div class="login-id-label">비밀번호</div>
    <input type="password" name="password" id="password" class="login-input-box">

    <button id="login-id-submit" onclick="onLogin()">로그인</button>
  </div>
  <div id="login-failed" style="display:none" class="alert alert-danger" role="alert">로그인에 실패하였습니다.</div>
</div>
</body>
<script>
  $(document).ready(function () {
    // 토큰 삭제
    Cookies.remove('Authorization', {path: '/'});
  });

  const href = location.href;
  const queryString = href.substring(href.indexOf("?") + 1)
  if (queryString === 'error') {
    const errorDiv = document.getElementById('login-failed');
    errorDiv.style.display = 'block';
  }

  const host = 'http://' + window.location.host;

  function onLogin() {
    let username = $('#username').val();
    let password = $('#password').val();

    $.ajax({
      type: "POST",
      url: `/api/user/login`,
      contentType: "application/json",
      data: JSON.stringify({username: username, password: password}),
    })
            .done(function (res, status, xhr) {
              const token = xhr.getResponseHeader('Authorization');

              Cookies.set('Authorization', token, {path: '/'})

              $.ajaxPrefilter(function (options, originalOptions, jqXHR) {
                jqXHR.setRequestHeader('Authorization', token);
              });

              window.location.href = host;
            })
            .fail(function (jqXHR, textStatus) {
              alert("Login Fail");
              window.location.href = host + '/api/user/login-page?error'
            });
  }
</script>
</html>

 

 

 

REST API 키는 카테고리 중 [앱 키] 라는 곳에 있음.

 

아래와 같이 복사한 후 입력한다.

 

 

 

리다이렉트 url 쪽으로 카카오 서버에서 인가코드를 보내줍니다. 

그러므로, 인가 코드를 받는 Controller를 만들어 줘야 합니다.

 

 

 

 

 

 

1. Controller 코드 추가

    @GetMapping("/user/kakao/callback")
    public String kakaoLogin(@RequestParam String code, HttpServletResponse response) throws JsonProcessingException {
        String token = kakaoService.kakaoLogin(code); // 반환 값이 JWT 토큰

        Cookie cookie = new Cookie(JwtUtil.AUTHORIZATION_HEADER,token);
        cookie.setPath("/" );
        response.addCookie(cookie);

        return "redirect:/";
    }
메인 페이지로 이동시킬 것이므로 반환 타입은 String으로 합니다.
카카오 서버에서 보내주는 인가 코드를 @RequestParam으로 받을 예정입니다.
완료가 다 된 후에는 JWT 토큰을 생성하고 새로 생성한 쿠키에 해당 토큰을 넣고 해당 쿠키를 Response에 추가하여 반환할 것입니다.

또한, 해당 url을 통해 받은 인가 코드를 처리해 줄 Service 단이 필요합니다.

 

 

 

2. Service Class 추가

 

KakaoService.java

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;
    private final JwtUtil jwtUtil;

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

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

        return null;
    }



}
RestTemplate는 RestTemplateBuilder를 통해 만들 수 있습니다.
해당 프로젝트에서는 클래스를 따로 만들어 Bean을 수동으로 등록하여 사용할 것입니다.

 

 

 

3. RestTemplate 타입의 빈 수동 등록

RestTemplateConfig 클래스 추가

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))
                .setReadTimeout(Duration.ofSeconds(5))
                .build();
    }
    
}
스프링 부트에서 제공하는 기본 RestTemplateBuilder Bean을 클래스에 주입해야 합니다.
RestTemplate는 RestTemplateBuilder를 통해 생성이 되기 때문입니다.

RestTemplate는 추가적으로 옵션을 설정하는 경우가 많습니다.
그리하여, 보통 수동으로 Bean을 등록하는 방법을 많이 사용합니다.

RestTemplate로 외부 API 호출 시에 외부에서 일정 시간이 지나도 응답이 없을 때 무한 대기 상태 방지를 위해 애플리케이션(우리)쪽에서 강제 종료 실행

 

 

 

4. Service 코드 추가

1. 애플리케이션은 인증 코드로 카카오 서버에 토큰을 요청하고, 토큰을 전달 받습니다. 

-> getToken() 메서드 역할

2. 전달 받은 토큰으로 API를 호출하고, 카카오 서버는 토큰의 유효성을 확인합니다.

3. 카카오 서버는 토큰의 확인을 한 후 애플리케이션에 응답을 전달합니다.

 

 

1) 액세스 토큰 요청 메서드

    public 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:8081/api/user/kakao/callback"); // 애플리케이션 등록시 설정한 redirect_uri
        body.add("code",code); // 인가 코드

        RequestEntity<MultiValueMap<String, String>> requestEntity = RequestEntity
                .post(uri) // body 가 있으므로 post 메서드
                .headers(headers)
                .body(body);

        // HTTP 요청 보내기
        ResponseEntity<String> response = restTemplate.exchange(
                requestEntity,
                String.class // 반환값 타입은 String
        );
        
        // HTTP 응답 (JSON) -> 액세스 토큰 파싱
        JsonNode jsonNode = new ObjectMapper().readTree(response.getBody());
        return jsonNode.get("access_token").asText();
    }
인증 코드를 카카오 서버에 전달하여 토큰을 요청하는 메서드이다.

요청 URL을 만든 후에, HTTP Header와 Body를 생성합니다.
HTTP Body에 인증 코드도 같이 넣어줍니다.

RequestEntity<MultiValueMap<String, String>> 타입의 객체를 만든 후에
해당 객체에 메서드와 url, header, body를 세팅합니다.

ResponseEntity<String> 타입의 객체를 만들어서 HTTP 요청을 보냅니다.
해당 객체를 만들 때에는 위에서 만든 RequestEntity 객체와 반환 타입을 세팅합니다.

반홥타입을 String으로 하였기 때문에 Json으로 파싱해줍니다.
JsonNode jsonNode = new ObjectMapper().readTree(response.getBody());
파싱한 후, 엑세스 토큰을 꺼내옵니다.
jsonNode.get("access_token").asText();

카카오 API 문서를 보면 아래와 같이 필수 입력 사항들이 나오니, 이를 참고하여야 합니다.

 

 

 

2) 사용자 정보 요청

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);
}
인증 코드를 카카오 서버에 전달하여 사용자 정보를 받아오는 메서드이다.

요청 URL을 만든 후에, HTTP Header를 생성합니다.
Header에 JWT 토큰을 넣어줍니다.

RequestEntity<MultiValueMap<String, String>> 타입의 객체를 만든 후에
해당 객체에 메서드와 url, header, body를 세팅합니다.

ResponseEntity<String> 타입의 객체를 만들어서 HTTP 요청을 보냅니다.
해당 객체를 만들 때에는 위에서 만든 RequestEntity 객체와 반환 타입을 세팅합니다.

반홥타입을 String으로 하였기 때문에 Json으로 파싱해줍니다.
JsonNode jsonNode = new ObjectMapper().readTree(response.getBody());
파싱한 후, 엑세스 토큰을 꺼내옵니다.
jsonNode.get("access_token").asText();