401 Несанкционированная ошибка при использовании учетной записи сервера для выдачи себя за пользователя для доступа к его Google Диску.

Я пишу внутренний процесс на Java, который будет выдавать себя за пользователя и добавлять / удалять документы на их Google Диске.

Учетная запись сервера проходит аутентификацию правильно, но когда я пытаюсь выдать себя за пользователя, я получаю 401 Unauthorized error. Детали смотрите ниже.

Конфигурация

Я настроил учетную запись сервера следующим образом:

  • Создал проект под Google API и включил Google Drive API
  • Создал учетную запись службы с именем [email protected], установил роль как Актер учетной записи службы и предоставил ей делегирование на уровне домена. Он имеет идентификатор клиента 110xxxxxxxxx342
  • Я скачал ключевой файл P12

Я настроил домен с помощью экрана доступа клиента Manage API, чтобы разрешить 110xxxxxxxxx342 иметь область действия: https://www.googleapis.com/auth/drive.

Служба поддержки Google изучила мою конфигурацию и дала ей высокую оценку.

Тогда мой код выглядит следующим образом:

package com.dcm.sharingdocuments;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Map.Entry;

import com.google.api.client.auth.oauth2.TokenErrorResponse;
import com.google.api.client.auth.oauth2.TokenResponseException;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.services.drive.Drive;
import com.google.api.services.drive.DriveScopes;
import com.google.api.services.drive.model.FileList;

public class SharingDocumentsTest3 {

    private static final String SERVICE_ACCOUNT_EMAIL = " [email protected]";

    public static Drive getDriveService(String userEmail) throws Exception {

        File keyFile = new File("E:\\Projects\\Workspace\\Sharing Documents\\authentication\\AnotherTestKeyFile.p12");

        HttpTransport httpTransport = new NetHttpTransport();
        JacksonFactory jsonFactory = new JacksonFactory();

        List<String> SCOPES = Arrays.asList(DriveScopes.DRIVE_METADATA_READONLY);

        GoogleCredential credential = null;

        if (userEmail == null) {

            credential = new GoogleCredential.Builder().setTransport(httpTransport).setJsonFactory(jsonFactory)
                    .setServiceAccountId(SERVICE_ACCOUNT_EMAIL).setServiceAccountScopes(SCOPES)
                    .setServiceAccountPrivateKeyFromP12File(keyFile).build();

            credential.refreshToken();

        } else {

            credential = new GoogleCredential.Builder().setTransport(httpTransport).setJsonFactory(jsonFactory)
                    .setServiceAccountId(SERVICE_ACCOUNT_EMAIL).setServiceAccountScopes(SCOPES)
                    .setServiceAccountPrivateKeyFromP12File(keyFile).setServiceAccountUser(userEmail).build();

            credential.refreshToken();

        }

        Drive service = new Drive.Builder(httpTransport, jsonFactory, null).setHttpRequestInitializer(credential)
                .build();

        return service;

    }

    public static void main(String[] args) {

        SharingDocumentsTest3 sdt3 = new SharingDocumentsTest3();

        sdt3.execute();

    }

    private void execute() {

        try {

            Drive service = getDriveService(null);

            Drive services = getDriveService("[email protected]");

            displayFiles(services);

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    private void displayFiles(Drive service) throws Exception {

        FileList result = service.files().list().setPageSize(10).execute();
        List<com.google.api.services.drive.model.File> files = result.getFiles();

        if (files == null || files.size() == 0) {

            System.out.println("No files found.");

        } else {

            System.out.println("Files:");
            for (com.google.api.services.drive.model.File file : files) {
                Set<Entry<String, Object>> entries = file.entrySet();

                Iterator<Entry<String, Object>> it = entries.iterator();

                while (it.hasNext()) {
                    Entry<String, Object> entry = it.next();
                    String key = entry.getKey();
                    Object value = entry.getValue();

                    if (value instanceof String) {
                        System.out.println("\tKey = " + key + ", Value = " + (String) value);
                    } else {
                        System.out.println("\tKey = " + key + ", Value = " + value.toString());
                    }

                }

                System.out.printf("%s (%s)\n", file.getName(), file.getId());
            }
        }
    }
}

Когда я запускаю код, как указано выше, я получаю сообщение об ошибке:

    Mar 29, 2017 9:55:27 AM com.google.api.client.googleapis.services.AbstractGoogleClient <init>
    WARNING: Application name is not set. Call Builder#setApplicationName.
    com.google.api.client.auth.oauth2.TokenResponseException: 401 Unauthorized
        at com.google.api.client.auth.oauth2.TokenResponseException.from(TokenResponseException.java:105)
        at com.google.api.client.auth.oauth2.TokenRequest.executeUnparsed(TokenRequest.java:287)
        at com.google.api.client.auth.oauth2.TokenRequest.execute(TokenRequest.java:307)
        at com.google.api.client.googleapis.auth.oauth2.GoogleCredential.executeRefreshToken(GoogleCredential.java:384)
        at com.google.api.client.auth.oauth2.Credential.refreshToken(Credential.java:489)
        at com.dcm.sharingdocuments.SharingDocumentsTest3.getDriveService(SharingDocumentsTest3.java:50)
        at com.dcm.sharingdocuments.SharingDocumentsTest3.execute(SharingDocumentsTest3.java:75)
        at com.dcm.sharingdocuments.SharingDocumentsTest3.main(SharingDocumentsTest3.java:65)

Таким образом, код не работает в credential.refreshToken (), когда я устанавливаю setServiceAccountUser. Кажется, он успешно обновил токен, но я этого не сделал. Я пробовал различные комбинации этого кода - например, закомментировал строки refreshToken (), закомментировал строку getDriveService (null), но всякий раз, когда я пытаюсь использовать / обновлять учетные данные, полученные для олицетворенного пользователя, я получаю ошибку 401 Unauthorized.

Если я изменю код так, чтобы диск, полученный с помощью getDriveService (null), был передан в DisplayFiles (...), то я получу один файл в списке под названием «Начало работы». Таким образом, похоже, что авторизация учетной записи службы работает, и Google добавил свой файл по умолчанию на Диск для учетной записи сервера.

Я использую google-*1.22.0.jar файлы и Java 1.8 для выполнения вышеуказанного кода

Проблема, я думаю, заключается в том, как я настроил домен или как я пытаюсь выдать себя за пользователя, но мой код выглядит так же, как и многие примеры в Интернете, и служба поддержки Google, похоже, сообщает, что я правильно настроил домен.

Мы будем очень признательны за все, что вы можете предложить в качестве решения или следующего шага!


person Chris B.    schedule 29.03.2017    source источник
comment
Проверьте следующий вопрос: stackoverflow.com/questions/33602347/, где это, похоже, работает.   -  person Konrad    schedule 29.03.2017
comment
Привет, Конрад, большое спасибо за столь быстрый ответ. Я рассмотрел вопрос, но проблема пользователя решена путем удаления setServiceAccountUser (...), тем самым не выдавая себя за пользователя. Это то, что я тоже испытываю. Код запускается при условии, что я не включаю setServiceAccountUser (...), но мне нужно включить этот метод, чтобы олицетворять пользователя и получать доступ к его файлам.   -  person Chris B.    schedule 29.03.2017
comment
1. Вы должны позволить клиентской библиотеке заниматься обновлением вашего доступа. У этой учетной записи и учетной записи службы нет токенов обновления. 2. DriveScopes.DRIVE_METADATA_READONLY ‹- доступ только для чтения 3. Быстрый запуск учетной записи службы developers.google.com / identity / protocol / OAuth2ServiceAccount   -  person DaImTo    schedule 29.03.2017
comment
Привет, DalmTo, я согласен на обновление токенов. Это не то, чего я ожидал сделать, но в первую очередь как способ отловить ошибку как можно скорее. Я бы не ожидал сделать это в производственной среде для того, что я пытаюсь сделать. Я перепробовал все DriveScopes, включая all (), и каждый раз получаю одну и ту же ошибку. Я много раз читал страницу быстрого старта - и все же мой код не работает!   -  person Chris B.    schedule 29.03.2017
comment
Вам удалось это решить? Здесь та же проблема ...   -  person Victor Gomes    schedule 07.08.2017
comment
Привет, Виктор! К сожалению, решить проблему не удалось. Я тоже говорил со службой поддержки Google, но ничего не добился. С неохотой мне пришлось двигаться дальше, и теперь я использую Microsoft OneDrive для бизнеса.   -  person Chris B.    schedule 09.08.2017
comment
У меня такая же проблема, и это единственный вопрос о переполнении стека (и я их много читал), который, кажется, точно описывает мою проблему ...   -  person Alistair Jones    schedule 14.02.2018


Ответы (1)


Я долго зацикливался на этой проблеме и наконец нашел свою проблему. В консоли администратора "Управление доступом клиента API" определенно есть ошибка ...

Вы должны указать «Идентификатор клиента» (например, 110xxxxxxxxx342) в качестве имени клиента, а НЕ «Идентификатор учетной записи службы» (тот, который выглядит как электронное письмо). Теперь их документация верна, и они говорят в документации по использованию идентификатор клиента, я должен дать им это.

Итак, вот ошибка. Когда я перешел на экран управления API, я увидел «Пример: www.example.com». Я ввел ID учетной записи службы, думая, что формат адреса электронной почты соответствует "www.example.com" лучше, чем идентификатор клиента. Я нажал «Авторизоваться», и запись была принята, и все было хорошо. Результат выглядит так:

Неправильно настроенный снимок экрана

Он даже сгенерировал идентификатор клиента из идентификатора услуги! Большой! За исключением того, что мой код выдает ошибку 401 каждый раз, когда я пытаюсь подключиться к setServiceUser().

Если я вернусь в консоль управления клиентским доступом API и удалю предыдущую запись и выполню те же действия, за исключением использования идентификатора клиента вместо идентификатора службы. Результат такой:

Виден идентичный снимок экрана, но правильно настроенный

Точно так же, но теперь я не получаю ошибку 401. Таким образом, НЕЛЬЗЯ взглянуть на консоль и узнать, успешно ли она настроена. Я тестировал это 3 раза, чтобы убедиться, что не схожу с ума ...

person Alistair Jones    schedule 14.02.2018
comment
Привет, Алистер, отличные новости! Хорошо подмечено. Я попробую это на себе, но думаю, вы его взломали. - person Chris B.; 20.02.2018
comment
У меня такая же ошибка (401 при делегировании), и я выполнил все те же шаги, включая тот, который решил ее за вас. Скорее всего, я пропустил шаг в конфигурации GSuite или GCP, но я не могу понять это. Есть ли шанс, что я смогу побеспокоить вас опубликовать обновление, включающее каждый из этих шагов GSuite и GCP? Спасибо! :) - person Patrick Lightbody; 11.12.2019
comment
Быстрое обновление на случай, если у других, таких как я, все еще есть проблемы: я обнаружил, что мне нужно возиться с авторизованными оценками API. Мой тестовый вызов API заключался в том, чтобы вывести список записей CalendarList, и я предоставил googleapis.com/auth/calendar, чего должно быть хоть отбавляй. Но я получал 401, пока не расширил область действия до https://www.googleapis.com/auth/calendar,https://www.googleapis.com/auth/calendar.readonly, что, честно говоря, не имеет для меня никакого смысла, но это сработало! - person Patrick Lightbody; 11.12.2019