레츠고✨

[Spring boot] Google Calendar API 연동하기 본문

Backend/Spring

[Spring boot] Google Calendar API 연동하기

소냐. 2024. 5. 5. 19:08

 

 

Google 개발자 문서에서 java 버전을 참고하여 spring boot를 이용해 구글 캘린더 API를 연동하는 과정을 정리해보려합니다!

과정을 크게 두 가지로 나누면 다음과 같습니다.

1. 구글 API를 사용하기 위해 구글 개발자 사이트에서 환경설정

2. Spring boot 개발

 

개발 전 환경 설정 및 사전 준비


환경 설정하기

  • API 사용 설정
  • OAuth 동의 화면 구성
  • 사용자 인증 정보 승인 - 데스크톱 애플리케이션

개발을 하기 전, Google API 를 사용하기 위해 구글 Cloud 서비스 사이트에 접속하여 이런 저런 설정을 해주어야 합니다.

 

Google 클라우드 플랫폼

로그인 Google 클라우드 플랫폼으로 이동

accounts.google.com

 

1. 프로젝트 생성

새 프로젝트 생성 → 프로젝트 이름 작성 → 만들기

 

2. API 사용 설정

다양한 Google API 중에 사용할 API를 찾아 '사용설정'을 눌러줍니다.

 

3. 사용자 인증 정보 생성

OAuth 2.0 클라이언트ID - 데스크톱 애플리케이션(인증에 사용될 키 정보)를 생성합니다. 이 때 키 정보를 저장한 json 파일은 개발할 때 인증을 받아오기 위해 필요합니다.

→ 다운받아서 'credentials.json' 으로 이름 변경

→ 추후에 생성할 spring  프로젝트의 작업 디렉토리에 추가 (resources 디렉토리 하위에)

참고: OAuth 2.0 클라이언트 ID는 플랫폼이 달라지면 각 플랫폼마다 ID를 생성해야 합니다.

 

4. 앱 사용 동의화면 구성

  • Google 사용자에게 인증을 요청할 때, 정보 조회 및 사용에 동의하는지 묻는 화면이 나오는데 그 화면을 구성하기 위한 설정들입니다.
  • 2단계 ‘범위’에서는 어떤 범위의 정보들까지 허용할 것인지 설정합니다.

Spring boot 개발 - Google Calendar API 연동


구글 공식 문서를 참고하여 환경 설정은 모두 완료 했고,

마지막 단계에서 인증 정보에 대한 JSON 파일도 credentials.json 로 저장하여 resources 디렉토리 아래에 추가하였습니다. 이제 본격적으로 개발을 시작합니다.

1.  Google API 의존성 추가

저는 Groovy-Kotlin 버전으로 Spring 프로젝트를 생성하였기 때문에 의존성 파일을 Kotlin으로 작성해야 합니다.

그래서 공식 문서에 나와 있는 다음과 같은 부분을

build.gradle.kts파일에 추가합니다.

 

dependencies {
    implementation("com.google.api-client:google-api-client:2.0.0")
    implementation("com.google.oauth-client:google-oauth-client-jetty:1.34.1")
    implementation("com.google.apis:google-api-services-calendar:v3-rev20220715-2.0.0")
}

 

위와 같이 의존성 코드를 추가한 뒤에 '코끼리'를 누르고 빌드를 기다립니다.

빌드가 완려되면 Dependencies 에 google.api, google.oauth 로 시작하는 의존성들이 추가된 것을 확인할 수 있습니다. 

 

 

* 참고로 Gradle 버전은 다음과 같은 형식으로 넣어주면 됩니다.

dependencies {
    implementation 'com.google.api-client:google-api-client:2.0.0'
    implementation 'com.google.oauth-client:google-oauth-client-jetty:1.34.1'
    implementation 'com.google.apis:google-api-services-calendar:v3-rev20220715-2.0.0'
}

 

2. Spring 로직 구현

이제 공식 문서에 나온 연동 샘플 코드를 활용해서 구글 캘린더에 정보를 요청하는 로직을 구현해보겠습니다.

 

> java 버전 샘플코드

더보기
public class CalendarQuickstart {
  
  private static final String APPLICATION_NAME = "Google Calendar API Java Quickstart";
  private static final JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance();
  private static final String TOKENS_DIRECTORY_PATH = "tokens";
	private static final List<String> SCOPES =
      Collections.singletonList(CalendarScopes.CALENDAR_READONLY);
  private static final String CREDENTIALS_FILE_PATH = "/credentials.json";

  
  private static Credential getCredentials(final NetHttpTransport HTTP_TRANSPORT)      throws IOException {
    // Load client secrets.
    InputStream in = CalendarQuickstart.class.getResourceAsStream(CREDENTIALS_FILE_PATH);
    if (in == null) {
      throw new FileNotFoundException("Resource not found: " + CREDENTIALS_FILE_PATH);
    }
    GoogleClientSecrets clientSecrets =
        GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in));

    // Build flow and trigger user authorization request.
    GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
        HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES)
        .setDataStoreFactory(new FileDataStoreFactory(new java.io.File(TOKENS_DIRECTORY_PATH)))
        .setAccessType("offline")
        .build();
    
    LocalServerReceiver receiver = new LocalServerReceiver.Builder().setPort(8888).build();
    Credential credential = new AuthorizationCodeInstalledApp(flow, receiver).authorize("user");
    
    //returns an authorized Credential object.
    return credential;
  }

  public static void main(String... args) throws IOException, GeneralSecurityException {
    // Build a new authorized API client service.
    final NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
    Calendar service =
        new Calendar.Builder(HTTP_TRANSPORT, JSON_FACTORY, getCredentials(HTTP_TRANSPORT))
            .setApplicationName(APPLICATION_NAME)
            .build();

    // List the next 10 events from the primary calendar.
    DateTime now = new DateTime(System.currentTimeMillis());
    Events events = service.events().list("primary")
        .setMaxResults(10)
        .setTimeMin(now)
        .setOrderBy("startTime")
        .setSingleEvents(true)
        .execute();
    List<Event> items = events.getItems();
    if (items.isEmpty()) {
      System.out.println("No upcoming events found.");
    } else {
      System.out.println("Upcoming events");
      for (Event event : items) {
        DateTime start = event.getStart().getDateTime();
        if (start == null) {
          start = event.getStart().getDate();
        }
        System.out.printf("%s (%s)\n", event.getSummary(), start);
      }
    }
  }
}

 

로직의 큰 순서를 정리하면 다음과 같습니다.

 

1) Google Cloud Service와 통신하기 위해 HTTP 통신 객체를 생성합니다.

2) HTTP 객체와 Google API 사용자 인증 정보가 담긴 credentials.json 파일을 이용해 해당 계정의
Google API 인증 정보를 획득합니다.

3) 획득한 인증 정보를 통해 Google Calendar API 객체를 가져옵니다.

4) 캘린더 객체에서 원하는 정보를 파싱하여 출력합니다.

 

위 로직을 Spring에서 구현하기 위해 Controller , Service 클래스를 생성해 로직을 나눠보겠습니다.

 

Controller 

  • Get Mapping 함수 : 요청 uri에 따라 함수 실행
  • Service Bean 클래스 주입
  • getEvents() 함수 생성
    • 캘린더 이벤트 객체를 결과로 받는 함수
    • 캘린더 이벤트 객체를 ResponseEntity 에 담아 반환한다.
/**
 * 구글 캘린더 API 이벤트를 가져오는 함수 - postman 테스트용
 */
@GetMapping("/events")
public ResponseEntity<List<Event>> getEvents() throws IOException, GeneralSecurityException {
    List<Event> events = calendarService.getEvents();
    return ResponseEntity.ok(events);
}

 

 

Service

  • 대부분의 핵심 로직을 담당
  • getCredentials() 함수 : 인증 정보 획득
  • getEvents() 함수 : 캘린더 객체 API 생성
    • HTTP 통신 객체 생성
    • 인증 정보 획득
    • Google 캘린더 객체 생성
    • 캘린더 객체에서 Event 추출하여 반환

 

getCredentials()

/**
 * 구글 캘린더 API 인증 정보를 가져오는 함수
 * @return Credential
 */
private static Credential getCredentials(final NetHttpTransport HTTP_TRANSPORT) throws IOException {
    // 인증 정보 파일 로드
    InputStream in = CalendarService.class.getResourceAsStream(CREDENTIALS_FILE_PATH);
    if (in == null) {
        throw new FileNotFoundException("Resource not found: " + CREDENTIALS_FILE_PATH);
    }

    // 인증 정보 로드
    GoogleClientSecrets clientSecrets =
            GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in));

    // 인증 정보 흭득
    GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
            HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES)
            .setDataStoreFactory(new FileDataStoreFactory(new java.io.File(TOKENS_DIRECTORY_PATH)))
            .setAccessType("offline")
            .build();

    // 인증 정보 반환
    LocalServerReceiver receiver = new LocalServerReceiver.Builder().setPort(8888).build();
    Credential credential = new AuthorizationCodeInstalledApp(flow, receiver).authorize("user");
    return credential;
}

 

 

getEvents()

/**
 * 구글 캘린더 이벤트를 가져오는 함수
 * @return List<Event>
 */
public List<Event> getEvents() throws IOException, GeneralSecurityException {
    // HTTP 통신 객체 생성
    final NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
    
    // 인증 정보 흭득
    Credential credential = getCredentials(HTTP_TRANSPORT);
    
    // 구글 캘린더 API 객체 생성
    Calendar service = new Calendar.Builder(HTTP_TRANSPORT, JSON_FACTORY, credential)
            .setApplicationName(APPLICATION_NAME)
            .build();
    
    // 이벤트 리스트 흭득
    DateTime now = new DateTime(System.currentTimeMillis());
    Events events = service.events().list(CALENDAR_ID)
            .setTimeMin(now)
            .setOrderBy("startTime")
            .setSingleEvents(true)
            .execute();
    return events.getItems();
}

 

 

 

캘린더 이벤트 정보가 잘 반환되는지 실행 및 테스트하기


1. 본인의 구글 정보 입력

1) 구글 사용자 인증 정보 파일 credentials.json 파일을 resources 디렉토리 아래에 추가합니다.

2) 조회하고 싶은 캘린더 ID 를 application.properties 파일의 calendar.id 부분에 입력합니다.

primary 는 구글의 기본 캘린더를 의미합니다. (디폴트 캘린더 아이디)

기본 캘린더 이외의 따로 추가했던 캘린더의 정보를 보고 싶다면 해당 캘린더의 ID를 찾아서 입력해주세요!
(구글 캘린더 설정에서 찾을 수 있음)

 

2. 프로젝트 실행

프로젝트를 실행하고 http://localhost:8080/events 로 요청하면 프로젝트 터미널에 구글 사용자 인증 홈페이지 주소가 나옵니다.

 

해당 링크를 클릭하면 구글 로그인 창이 나오고

여기서 본인의 구글 계정으로 로그인하면 해당 계정의 입력했던 캘린더 ID에 해당하는 캘린더의 이벤트들이 불려옵니다.

 

이렇게 한번 구글 계정을 인증하고 나면 프로젝트 내에 'token' 폴더가 생성되고 인증 토큰이 저장됩니다.

그래서 동일한 요청을 다시 해도 구글 계정 인증을 또 할 필요는 없습니다.

근데 만약 구글 API 설정을 바꿔서 다시 계정 인증을 해야 되거나

토큰 인증 기간이 만료되어서 인증이 실패한다면

이 폴더를 삭제하고 다시 요청하면 됩니다!

 

3. 캘린더 이벤트 정보 확인하기 - Postman

위에서 프로젝트가 정상적으로 실행되고, 구글 로그인까지 했다면

이제 Postman에서 데이터가 잘 넘어오는지 확인해보겠습니다.

 

요청

응답

 

요청이 성공한다면 위처럼 Status 200 OK 가 뜨고 내 구글 캘린더의 내용이 json 파일로 반환됩니다.

이 객체의 정보들 중에 내가 필요한 정보들만 파싱해서 웹사이트에 출력되게끔 하면 됩니다.

 

 

트러블 슈팅


1. FileNotFoundException : 인증 정보 파일을 찾지 못하는 에러

 

실행하면 자꾸 파일을 찾지 못하는 에러가 나왔습니다.

구글 공식 문서에 해당 에러에 대한 언급이 나온 부분을 살펴보았습니다.

 

https://developers.google.com/calendar/api/troubleshoot-authentication-authorization?hl=ko

 

그래서 사용자 인증 정보를 살펴보았지만 별다른 문제점은 확인할 수 없었고

결과적으로 제 경우에는 '파일의 경로'가 잘못 입력된 것이 문제였습니다.

인증 정보 파일인 credentials.json 파일을 잘못된 경로에 저장했기 때문입니다.
처음에 저는 java 폴더 아래 Controller, Service 클래스들과 동일한 디렉토리 아래에 저장했었습니다.
credentials.json 파일은 확장자가 json이기 때문에 resources 폴더 아래에 위치해야 합니다.
그리고 파일 경로는 './credentials.json' 입니다.

 

이유는 java 폴더의 경우는 class, interface 등 java 코드로 이루어진 파일들을 보관하는 곳이고,
resources 폴더는 java 코드 이외의 자원들 (html, json, img, js, yml, properties)을 보관하는 곳이기 때문입니다.

따라서 Spring 은 json 파일의 위치를 찾을 때 resource 폴더부터 찾기 시작합니다. 그래서 java 폴더 하위에 저장했을 때 자꾸 파일을 찾지 못하는 오류가 났던 것입니다.  

 

2. @Value Annotaion : 이 설정 파일의 변수를 가져오지 못함

그리고 calendar id 값을 properties 파일에 따로 저장해서

@value 애노테이션을 통해 가져오려고 했습니다.

 

하지만 자꾸 변수를 가져오지 못하는 문제가 발생했습니다.

스프링 빈 주입이 잘못되어 Value 애노테이션이 잘 적용되지 않았나 싶어 관련 리서치를 하다가

발견했던 원인은 Value import 경로가 잘못되었다는 것이었습니다.

 

살펴보니

import com.google.api.client.util.Value;

 

이렇게 import가 되어 있었는데 이게 아니라

 

import org.springframework.beans.factory.annotation.Value;

 

이렇게 import 가 되어 있어야 합니다.

 

참고

https://gyucheolk.tistory.com/88

 

[Spring] @Value 애노테이션 null 점검

Spring에서 @Value 애노테이션은 설정 파일(yml, properties)에 있는 정보를 가져오는데 주로 사용됩니다. @Value로 설정 파일 값을 가져오는 변수가 null인 경우 점검해보아야 하는 부분을 정리하였습니다

gyucheolk.tistory.com

 

 

 

더 발전시키면 좋을 것 같은 과제


1. 캘린더 리스트 객체 정보 파악

지금은 조회하고 싶은 캘린더 ID를 찾아서 프로젝트에 하드코딩해서 넣어주어야 합니다.

  • 구글 계정의 캘린더 리스트를 가져와서 그 중 선택한 캘린더의 ID를 넘겨주면 해당 캘린더의 이벤트들이 조회되게끔 하면 좋을 것 같습니다.
  • 그러기 위해서는 구글 캘린더 객체 구조에 대한 정리가 더 필요할 것 같습니다.
  • 그리고 캘린더를 선택하는 웹 화면이 필요합니다.

2. 구글 로그인 화면

현재는 API를 요청하면 구글 로그인 화면이 자동으로 뜨는 것이 아니라 프로젝트 터미널에서 링크를 통해 들어가야 합니다.

  • 실행시키면 바로 로그인 화면이 리다이렉션 되게끔 웹 구성을 하며 더욱 편리할 것 같습니다.

 

 

여기까지 구글 캘린더 API 를 스프링 부트를 이용해서 연동하고 테스트하는 과정을 정리해보았습니다!

간단한 레벨까지만 개발했기 때문에 아직은 불편한 부분이 많지만 기회가 되면

이번 기회에 공부한 내용을 나중에 프로젝트에도 적용할 수 있으면 좋을 것 같습니다.

 

대체로 구글 공식 문서와 Chat GPT를 활용해서 정리했는데, 혹시 잘못된 내용이 있다면 댓글로 알려주세요!  

 

728x90