etc

[230530] [google drive api] java api 사용하기 (2) - 인증토큰 발급

hjk927 2023. 5. 30. 20:06

 

이제서야 2편을 쓴다... ㅋㅋㅋ 

나는 파일검색 말고는 안 써봐서 파일 검색 관련된 내용만 포스팅을 할 예정이다. 

그런데 어떻게 내용을 써야할지 모르겠다. 뭔가 매뉴얼을 보면 다 있는 내용이라서... 강의같은 글보다는 개발기 같은 글이 될 것 같다. 

적다보니 내용이 길어져서 2편에서 인증토큰 발급 과정을 적고 3편에서 검색하는 법을 적으려고 한다. 

 

인증토큰 발급 소스는 굳이 DRIVE api가 아니더라도 여러 구글 api에 응용할 수 있다. 


 

알다시피 구글 api 사용 가이드 링크는 아래와 같다. 

 

https://developers.google.com/drive/api/guides/about-sdk 

 

Google Drive API 소개  |  Google for Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. Switch to English 의견 보내기 Google Drive API 소개 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Google Drive

developers.google.com

 

그중에서 Search for files & folders 항목이 내가 하고자 하는 파일 검색 관련 내용이다. 

 

음... 일단 내가 작성한 전체 코드를 보자. 

 

참고로 gradle을 사용하였으며 아래와 같이 추가하였다. 

    // Google drive api
    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-drive:v3-rev20220815-2.0.0'

 

 

아래가 전체 코드다. 

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import java.util.List;

import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp;
import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.client.util.store.FileDataStoreFactory;
import com.google.api.services.drive.Drive;
import com.google.api.services.drive.DriveScopes;
import com.google.api.services.drive.model.File;
import com.google.api.services.drive.model.FileList;

public class DriveQuickstart {
	
	private static final String APPLICATION_NAME = "Google Drive API Java Quickstart";
	private static final JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance();
	
	// 인증토큰 저장할 path
	private static final String TOKENS_DIRECTORY_PATH = "tokens";

	// API 사용 범위 
	private static final List<String> SCOPES = Arrays.asList(DriveScopes.DRIVE_METADATA_READONLY, DriveScopes.DRIVE_READONLY);
	
	// OAuth 클라이언트 키 저장 위치 
	private static final String CREDENTIALS_FILE_PATH = "/credentials.json";

	/**
	 * 인증토큰을 가져온다. 
	 *
	 * @param HTTP_TRANSPORT The network HTTP Transport.
	 * @return An authorized Credential object.
	 * @throws IOException If the credentials.json file cannot be found.
	 */
	private static Credential getCredentials(final NetHttpTransport HTTP_TRANSPORT) throws IOException {
		// 1. OAuth 클라이언트 키 파일이 있는지 체크 
		InputStream in = DriveQuickstart.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));

		// 2. 유저 인증 플로우 생성 및 인증토큰 가져오기 
		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;
	}

	public static void main(String... args) throws IOException, GeneralSecurityException {
		// 1. Drive 객체 생성 
		final NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
		Drive service = new Drive.Builder(HTTP_TRANSPORT, JSON_FACTORY, getCredentials(HTTP_TRANSPORT))
				.setApplicationName(APPLICATION_NAME)
				.build();

		// 2. 검색 
        String pageToken = null;
       
        do {
            try {
                FileList list = service.files().list()
                        .setQ("name contains 'google' or fullText contains 'google'")
                        .setCorpora("allDrives")
                        .setIncludeItemsFromAllDrives(true)
                        .setSupportsAllDrives(true)
                        .setIncludeTeamDriveItems(true)
                        .setSupportsTeamDrives(true)
                        .setPageSize(10)
                        .setFields("nextPageToken, files(id, name, owners, parents, createdTime, description, mimeType, "
                                + "webViewLink, webContentLink, fileExtension, iconLink, thumbnailLink, driveId, originalFilename)")
                        .setPageToken(pageToken).execute();
               
                pageToken = list.getNextPageToken();
               
                for(File file : list.getFiles()) {
                    System.out.print("file name: " + file.getName());                    
                }
               
            } catch (Exception e) {
               	e.printStackTrace();
            }
        } while(pageToken != null);
           
	}
}

 

 

프로그램 실행흐름대로 살펴보자. 먼저 main 함수 상단이다. 

public static void main(String... args) throws IOException, GeneralSecurityException {
		// 1. Drive 객체 생성 
		final NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
		Drive service = new Drive.Builder(HTTP_TRANSPORT, JSON_FACTORY, getCredentials(HTTP_TRANSPORT))
				.setApplicationName(APPLICATION_NAME)
				.build();

드라이브를 활용하고 싶다면 먼저 Drive 객체를 생성해야 한다. (동일한 맥락에서 구글 슬라이드에서는 Slides, 구글 문서는 Docs를 생성해야 한다.) 

 

Drive 객체는... javadoc에 따르면 Drive에 관련된 서비스(파일 업로드, 다운로드, 검색 등등......)를 관리하는 객체다. com.google.api.services.drive.model 아래에도 Drive 객체가 또 있는데 그거랑은 다르다. 

Drive.Builder 를 사용해서 필드값을 초기화하고 생성할 수 있다고 한다. 

코드에서 new Driver.Builder()로 객체를 만든다. 생성자에 HTTP_TRANSPORT, JSON_FACTORY를 넣어주는데 사실 이 부분은 나도 코드를 그대로 가져다 쓴 거라서 다른 옵션이 있는지 모르겠다. 

세번째 인자에서 getCredentials(HTTP_TRANSPORT) 의 반환값을 사용한다. 해당 메소드를 자세히 보자. 

 

/**
	 * 인증토큰을 가져온다. 
	 *
	 * @param HTTP_TRANSPORT The network HTTP Transport.
	 * @return An authorized Credential object.
	 * @throws IOException If the credentials.json file cannot be found.
	 */
	private static Credential getCredentials(final NetHttpTransport HTTP_TRANSPORT) throws IOException {
		// 1. OAuth 클라이언트 키 파일이 있는지 체크 
		InputStream in = DriveQuickstart.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));

		// 2. 유저 인증 플로우 생성 및 인증토큰 가져오기 
		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;
	}

Credential이라는 걸 return한다. 

OAuth에서 사용하는 토큰을 관리하고, 만료되었을 때 자동으로 갱신해주는 역할을 한다고 한다. 

 

Credential 생성 로직은 아래와 같다. 

 

1. api 키를 불러와서 GoogleClientSecrets 객체로 저장한다. 

InputStream in = DriveQuickstart.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));

CREDENTIALS_FILE_PATH 에 저장된 OAuth 클라이언트 키 파일을 가져온다. 여기서 말하는 키 파일이란 이전 포스팅에OAuth 클라이언트 ID를 생성한 뒤에 저장한 키파일을 말한다. 

 

분실했다고 해도 구글 클라우드 콘솔에서 재발급받을 수 있다. 저장버튼을 눌러서 다시 저장하면 된다. 

저장한 키 파일 이름을 credentials.json으로 바꾼 뒤에 프로젝트 경로에서 resources 아래에 저장했다. 

 

2. 유저 인증 플로우를 생성하고 인증 토큰을 가져온다. 

// 2. 유저 인증 플로우 생성 및 인증토큰 가져오기 
		GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(HTTP_TRANSPORT, JSON_FACTORY,
				clientSecrets, SCOPES)
						.setDataStoreFactory(new FileDataStoreFactory(new java.io.File(TOKENS_DIRECTORY_PATH)))
						.setAccessType("offline")
						.build();

먼저 GoogleAuthorizationCodeFlow를 생성한다. 여기서 생성할 때 아까 불러온 clientSecrets를 넘겨준다.

또 생성할 때 넘겨주는 SCOPES 값이 중요하다. OAuth 인증 시에 사용할 api 범위를 말한다. 

 

내가 설정한 SCOPE 값은 아래와 같다. 

	// API 사용 범위 
	private static final List<String> SCOPES = Arrays.asList(DriveScopes.DRIVE_METADATA_READONLY, DriveScopes.DRIVE_READONLY);

검색만 할 것이라서 DRIVE_METADATA_READONLY, DRIVE_READONLY 2개만 추가했다. 

(사실 DRIVE_READONLY 하나만 있어도 된다.) 

api에서 파일 추가 기능도 필요하다면 DRIVE 권한까지 추가하면 된다. 

	// API 사용 범위 
	private static final List<String> SCOPES = Arrays.asList(DriveScopes.DRIVE_METADATA_READONLY, DriveScopes.DRIVE_READONLY
			, DriveScopes.DRIVE);

 

GoogleAuthorizationCodeFlow를 생성한 뒤에는 LocalServerReceiver를 생성한다. 

나중에 receiver를 생성할 때 설정한 포트번호와 url로 인증페이지를 리다이렉트해준다. (나는 설정하지 않았지만 setHost() 사용해서 url도 추가해줄 수 있다. ) 

		LocalServerReceiver receiver = new LocalServerReceiver.Builder().setPort(8888).build();
		
		Credential credential = new AuthorizationCodeInstalledApp(flow, receiver).authorize("user");

OAuth 설정 페이지에서 url을 추가해주지 않으면 error가 발생하니까 주의해야 한다. 

 

여기까지 하고 코드를 실행하면 리다이렉트 페이지를 얻을 수 있다! 

그런데 실행했더니 오류가 난다ㅋㅋㅋ

redirect_uri_mismatch...... 

업무 중에 했을 때는 됐는데...... 뭔가 잘못 설정한 모양이다. 

 

다시 살펴보자. 

 

 

1) 리디렉션 URL 

콘솔창에 찍힌 리디렉션 URL을 잘 보니까 url이 http://127.0.0.1:8888/Callback이다. 

OAuth 설정 페이지에서 url 정보를 변경해보자. 

http://127.0.0.1:8888/Callback 으로 설정해서 저장 => 클라이언트 키 재발급 => 프로젝트 resources 경로 아래에 있는 credentials.json 파일과 교체 => 재실행 

 

이제 구글 로그인 창은 뜨는데 로그인을 하면 액세스가 차단되었다고 나온다. 

내 앱이 테스트상태인 모양이다. 

 

OAuth 동의 화면에 들어가서 테스트 사용자를 추가해줬다. 

 

재실행하면 액세스 권한이 있다고 나온다! 

계속 버튼을 누른다. 

그러면 내가 설정한 SCOPES에 대한 액세스 허가를 요청한다. 

모두 체크하고 계속 버튼을 누르면 인증은 끝난다. 

 

인증이 끝나면 프로젝트 경로 /tokens 아래에 토큰이 생긴 걸 확인할 수 있다! 토큰은 일정 시간이 지날 때마다 알아서 갱신된다. 

경로를 바꾸고 싶다면 소스에 있는 TOKENS_DIRECTORY_PATH 전역변수 값만 바꿔주면 된다. 

 

사용자 인증은 한 번만 받고 나면 그 뒤로는 별도 인증 없이 액세스가 허가된 걸로 처리되어서 사용할 수 있다. 

이제 프로그램을 실행하면 사용자 인증 페이지 리다이렉트 과정은 생략되고 검색 결과가 나온다. 

내 드라이브에서 검색한 검색 결과다. 

 

검색 API 사용법은 다음 포스팅에서 설명한다.