본문 바로가기
iOS

Google Drive API 사용하기- iOS

by vapor3965 2021. 9. 9.

목차

     

    앱의 데이터를 백업하고자 플래폼을 이용하려고 한다.

    하나의 서버를 두어서 모든 사용자들의 데이터를 백업하는 것이 아닌, 

    사용자의 계정의 클라우드에 저장하고자 한다. 

    그래서 Google Cloud API를 이용하려고 한다. 

     

    우선 GoogleDrive API는 다음과 같이 제한이 걸려있다.

     

    아무리 찾아봐도 Google Drive API의 비용관련 공식글을 찾을 수가 없었다.

    스택오버플로우에서는 다음과 같이 제한에는 무료로 누구든지 사용할 수 있다라고 한다. 

     

    ... 굉장히 문서들이 뒤죽 박죽이라는 느낌이 강하다.  여기 찾고, 저기 찾고 ... 

     


    라이브러리 설치

    우선, 라이브러리들을 이용해서 한다면,  다음 두 개의 라이브러리를 cocoapod을 통해 설치한다. 

    GoogleSignIn은 구글로 로그인하기 위해 사용된다. 구글로 로그인하여 사용자의 권한을 얻을 수 있다.

    GoogleAPIClientForREST GoogleCloudAPI를 라이브러리로 이용하기 위함이다. 

    이 라이브러리를 이용하여 CloudAPI를 이용하기 위해서는 GoogleSignIn를 이용아여 사용자의 권한을 참조해야한다. 

    pod 'GoogleAPIClientForREST/Drive'
    pod 'GoogleSignIn'

     

     


    구글 로그인하여 유저 정보 받아오기 

     

    우선,  Google Cloud Platform 에가서 프로젝트를 만든다. 

     

    그러기 위해서는 Google Login 이 필요하고,

    그에 따른 Google-SignIn 라이브러리를 설치해야한다. 

    https://developers.google.com/identity/sign-in/ios/start-integrating 여기에서 따라하면 된다. 

    가이드라인을 따라서, Cocoapod으로 설치했고,  

    clientID를 받아서, 프로젝트 - 타겟 - URLScheme에 거꾸로된 clientID를 넣어줬다.

     

    https://developers.google.com/identity/sign-in/ios/sign-in 마찬가지로 여기에서 하라는대로 하면 된다. 

    그리고 프로젝트에 설정코드와, AppDelegate에 코드들을 추가한다.

    발급받은 clientID를 이용하여 configuration을 초기화하고,  ( 어떤식으로든 코드를 작성해도 상관없다 ) 

    class Google {
        static func config() -> GIDConfiguration {
            return GIDConfiguration.init(clientID: CLIENTID)
        }
    }

    AppDelegate에  GIDSingIn 핸들링 코드를 추가한다. 

    func application(
      _ app: UIApplication,
      open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]
    ) -> Bool {
      var handled: Bool
    
      handled = GIDSignIn.sharedInstance.handle(url)
      if handled {
        return true
      }
    
      // Handle other custom URL types.
    
      // If not handled by this app, return false.
      return false
    }

     

     

     

    그리고 해당 라이브러리에서 제공하는 구글로그인 버튼을 만들고, 

    action을 추가하여 호출한다. 

    @IBAction func signIn(sender: Any) {
      GIDSignIn.sharedInstance.signIn(with: signInConfig, presenting: self) { user, error in
        guard error == nil else { return }
    
        // If sign in succeeded, display the app's main content View.
      }
    }

     

     

    scheme이 잘 맞는다면 정상적으로 로그인까지 되지만, 

    OAuth 동의화면 추가하지 않으면 로그인 후 실패했다고 뜬다. 

    그러므로  OAuth 동의화면까지 추가해야, 이제 구글로그인 하면 인증이 된다.

     

    그러면 이제 로그인 후 다시 앱으로 돌아오면 컴플리션 핸들러가 호출되면서, 사용자에 대한 정보를 접근할 수 있다. 

     

    앱에서 사용자 Google Drive 데이터에 접근하기 위해서는 접근 요청을 해야한다.

    사용자가 허락한다면, Access Token을 준다. 

    • access Token은 REST or gRPC 요청할 때 쓰인다. 
    @IBAction func signIn(sender: Any) {
        GIDSignIn.sharedInstance.signIn(with: Google.config(), presenting: self) { user, error in
        guard error == nil else { return }
            guard let user = user else { return }
            print(user.authentication.accessToken)
      }
    }

     

     

     


    사용자 정보 바탕으로 Drive API 이용

     

    Google Cloud Platform에 가서 자신의 프로젝트에 

     Google Drive API를 추가한다.

     

    우선, Google Drive 를 검색 및 다운로드 하려고 한다면, 추가적인 scope를 요청해야한다.

    관련된 scope들은 여기서 확인할 수 있다.

    사용자가 이 권한도 허락한다면 이제 다음 차례로 넘어갈 수 있다. 

    let driveScope = "https://www.googleapis.com/auth/drive"
    GIDSignIn.sharedInstance.addScopes([driveScope], presenting: self) { user, error in
        guard error == nil else { return }
        guard let user = user else { return }
    	
    }

     

     

    Google APIs iOS 라이브러리 를 이용하여 Google Drive API를 구현한 블로그를 찾았고, 

    그대로 따라하면 우선, 안되는점이 몇개 있다.

    login으로 accessToken을 받아와서 이를 이제 drive api에 이용할때 적용해야하는데, 

    그 코드가 아래 처럼 로그인 후, user를 이용하여 authorizer에 부여해야하는데, authorizer를 찾을 수가 없다. 

    또한 fetchrAuthorizer()를 찾을 수 없다.

    class Google {
        let service: GTLRDriveService
        init(_ user: GIDGoogleUser) {
            service = GTLRDriveService()
            service.authorizer = user.authentication.fetcherAuthorizer()
        }
        ... 
    }
     
    @IBAction func signIn(sender: Any) {
        GIDSignIn.sharedInstance.signIn(with: Google.config(), presenting: self) { user, error in
        guard error == nil else { return }
            guard let user = user else { return }
            print(user.authentication.accessToken)
            print(user.authentication.fetcherAuthorizer())
            let googleDriveService = Google(user)
      }
    }

     

     

    왜냐하면 그 코드는 objective c로 되어있기 때문에, 따로 브릿지헤더를 만들어 주지 않으면 보이지 않게된다. 

    그래서 브릿지헤더 파일을 만들고, 아래들을 추가한다. 

    #import <GTMSessionFetcher/GTMSessionFetcher.h>
    #import <GTMSessionFetcher/GTMSessionFetcherService.h>

     

    그럼 이제, 해당 라이브러리의 GTLRDriveService를 이용하여 업로드 및 검색, 다운로드를 할 수 있다. 

    여기까지 잘 된다면, 이제 다음부터는 쉽게 위의 블로그를 참고하여 코드를 이용하면 된다. 

     


    특정 파일 찾기 

    쿼리에 대한 string 예는 여기서 확인할 수 있다

    폴더만을 찾고자 한다면 q에  and mimeType = 'application/vnd.google-apps.folder' 를 추가한다. 

    extension Google: BackupApi {
        func fetchFile(_ name: String, _ completion: @escaping ((Result<GTLRDrive_File, BackupApiError>) -> Void )) {
            let query = GTLRDriveQuery_FilesList.query()
            query.pageSize = 1
            query.q = "name contains '\(name)'"
            
            service.executeQuery(query) { (ticket, results, error) in
                if let error = error {
                    print(error.localizedDescription)
                    
                    let invalid = BackupApiError.invalid(error.localizedDescription)
                    completion(.failure(invalid))
                    return
                }
                guard let file = (results as? GTLRDrive_FileList)?.files?.first else {
                    completion(.failure(.noFile("")))
                    return }
                completion(.success(file))
            }
        }
    }

     


    특정 파일 다운로드 

     

    파일을 찾은 다음, 찾은 파일로 다운로드해야한다.

        func downLoad(_ fileItem: GTLRDrive_File, completion: @escaping (Data?) -> Void )  {
            guard let fileID = fileItem.identifier else {
                return completion(nil)
            }
            
            self.service.executeQuery(GTLRDriveQuery_FilesGet.queryForMedia(withFileId: fileID)) { (ticket, file, error) in
                guard let data = (file as? GTLRDataObject)?.data else {
                    return completion(nil)
                }
                completion(data)
            }
        }

     


    파일 업로드

     

    파일을 작성할 때는 MIME타입을 따라야한다.

    나는 앱의 데이터를 JSON파일로 저장할 것이므로, 

    MIME 타입은  application/json  으로 정한다. 

        func uploadFile(data: Data, name: String, _ completion: @escaping (Bool) -> Void ) {
            let file = GTLRDrive_File()
            file.name = name
            let params = GTLRUploadParameters(data: data, mimeType: "application/json")
            params.shouldUploadWithSingleRequest = true
            
            let query = GTLRDriveQuery_FilesCreate.query(withObject: file, uploadParameters: params)
            query.fields = "id"
            
            self.service.executeQuery(query, completionHandler: { (ticket, file, error) in
                if let error = error {
                    print(error.localizedDescription)
                    completion(false)
                    return
                } else {
                    completion(true)
                }
                
            })
        }

     

    코드가 정상적으로 동작한다면, 이제 로그인한 Google Drive에 내가 정한 이름으로 파일이 생성된다! 

     

     

     


     

    OAuth2 신청 

    만약 앱이 구글 드라이브를 이용해서 다른 사용자와 공유 되어진다면 OAuth2신청을 반드시 해야한다. 

    내 앱은 사용자만 혼자서 사용하기 때문에 굳이 OAuth2 인증을 받지 않아도 된다

    꼭 사용자만 혼자서 사용하는 경우 외에도 다른 경우들도 예외적으로 인증을 받지 않아도 된다

     

    만약 그런 경우가 아니라면 꽤... 아래와 같은 복잡한 절차를 거쳐야 한다. 

    ( 나는 예외 경우를 몰랐어서, OAuth2 신청 진행하면서 계속 빼먹은 내용이있어서  구글팀과 메일을 주고 받으면서 알게 됐다.

    결국 인증받지 못하고 예외 경우를 선택했다. 

    더보기

    아래는 최대한 인증받기 위한 과정을 설명했다. 결국에는 인증을 받지 못했지만, 어느정도 필요한 내용을 쓰려고 한다. 

    한번에 성공하지 못해도 상관없다. 계속해서 구글팀이 어떤게 부족한지 메일로 알려준다. 

     

    실제 앱에서 다른 사용자분들이 접속을 가능하게 하려면, 

    ( 테스트할 때는 테스트가능한 구글계정들만 테스트로 신청해놓으면 된다.  또는 ) 

    OAuth2 신청을 해야한다.

    신청을 하면 구글에서 검토하고 승인을 허락해준다. 

     

    제대로 만들지 않고 신청하면 추가적으로 고치라는 메일이 날라온다. 

    대략, 홈페이지와 유튜브 동영상 업로드.. 가 있다.

     

    우선 OAuth2 프로젝트는 홈페이지가 필요한데,

    홈페이지는 다음과 같은 내용이 필수적으로 포함되어야 한다.

    1. 사용자가 너의 앱에서 무엇을 할지 확실하게 하고, 

    2. 철저하게 너의 앱이 어떻게 사용자 경험을 향상시키는지 설명하고, 

    3. Privacy Policy에 대한 링크를 제공해야한다. 

    • 여기에는, 어떻게 앱이  구글 사용자 데이터에 접근하고, 사용하고 저장하고, 공유하는지를 명확하게 설명해야한다. 

    4. 너의 권한아래에 있는 도메인이여야 한다.

    5. 사용자에게 아주 쉽게 접근할 수 있어야 한다. 

    6. 공개적으로, 외부적으로 접근이 가능해져야 한다.

    • 만약 앱이 출시되기전까지 또는 접근이 안될때 까지 구글은 승인해줄 수 없다. 

    그리고 홈페이지 링크 다음을 따르면 안된다.

    1. 너가 너의 서브도메인에 대해 입증할 수 없는 써드파티 호스팅 플랫폼인경우,  예를들어 구글 플레이 스토어, 페이스북, 인스타그램, 트위터

    2. 로그인 또는 가입이 필요한 페이지 

    3. 테스트 URL 

     

    만약 인증을 받지 못한다면, 추가적인 scope를 이용하고자 할때 다음과 같이 인증받지 못한 앱이라면서 뜨지만, 

    사용자가 선택을 할 수 있다. 

    고급 통해서 누르고 

     

    쉬운과제캘린더(으)로 이동(안전하지 않음) 을 선택한다. 

     

    그럼 허용을 누르면 이제 앱이 사용자의 Google Drive에 접근할 수 있게 된다. 

     

    그럼 선택을 한 경우에는 내 앱이 사용자의 Google Drive의 권한을 갖게 되는데, 

    그럼 권한을 가졌으니 사용자가 권한을 풀도록 하는 방법도 기술해야겠다.

    구글 보안 진단 - 제3자 엑세스 를 통해서 해당 앱을 엑세스 권한 해제를 할 수 있다.

     

     


     

    참고 

     

     

    https://uynguyen.github.io/2019/02/15/Integrate-Google-Drive-to-iOS-app/ 

     

    Integrate Google Drive to iOS app

    At Fossil, I’ve had the chance to experiment with Google Drive integration, as a cloud bases storage. The main advantage of using Google Drive is to share with other members easily, with a good web-ba

    uynguyen.github.io

     

    https://github.com/google/google-api-objectivec-client-for-rest/issues/147

     

    GTLRService.h: authorizer property not imported in Swift module · Issue #147 · google/google-api-objectivec-client-for-rest

    In GTLRService.h The following property is not created when importing into a Swift project with import GoogleAPIClientForREST /** * An authorizer adds user authentication headers to the request as ...

    github.com

     

     

    댓글