Читаем Google-таблицы из web-приложения

в 12:44, , рубрики: Google API, google sheets api v4, java, oauth 2.0, spring boot, Программирование

Google имеет несколько версий API для доступа к своим электронным таблицам. Разберемся с тем, как прочитать данные из spreadsheet таблицы в web-приложении на java используя API версии 4.

Google-приложение

Создадим новый проект через google консоль.

создание нового проекта google

Активируем Sheets API.

поиск API для активации

Чтобы использовать выбранный API, нужно создать учетные данные. Вызывать API будем из браузера.

создание учетных данных

Создадим идентификатор клиента OAuth 2 и зададим ограничения по URL. Указывать надо как продуктивные, так и разработческие url.

создание идентификатора клиента

Окно запроса доступа тоже можно поднастроить, указав отображаемое имя, логотип и лицензию.

создание учетных данных 2

На выходе должен получиться файл учетных данных client_secrets.json. Готовый файл впоследствии необходимо разместить в ресурсах своего проекта.

Сценарии для авторизации

Идем дальше. Google Sheets API v4 поддерживает различные сценарии для авторизации с использованием авторизационного кода:

  • Для web-серверных приложений. Необходимо реализовать пару определеных сервлетов и добавить их в свой web.xml
  • Служебные учетные записи. Служебные учетные записи заводятся на google и предоставляют клиентам доступ к своим ресурсам, а не к ресурсам пользователя.
  • Для установленных приложений. Пример работы с API из консольного приложения.
  • Для клиентских приложений. В браузере формируем запрос на получение кода доступа, который потом можно обменять на токены.
  • Для android.

Для нас подходят сценарии для web- и для клиентских приложений. Схема работы для них общая:

процес использвание OAuth 2.0 для web-приложений.

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

Google oauth2 приложение

За основу web-приложения возьмем spring boot. Зависимости следующие:

        <!-- Google OAuth Client Library for Java. -->
        <dependency>
            <groupId>com.google.oauth-client</groupId>
            <artifactId>google-oauth-client-java6</artifactId>
            <version>${google.oauth.client.version}</version>
        </dependency>
 
        <!-- Google OAuth2 API V2 Rev124 1.22.0 -->
        <dependency>
            <groupId>com.google.apis</groupId>
            <artifactId>google-api-services-oauth2</artifactId>
            <version>${google.oauth2.version}</version>
        </dependency>
 
        <!-- Google Sheets API V4 Rev38 1.22.0 -->
        <dependency>
            <groupId>com.google.apis</groupId>
            <artifactId>google-api-services-sheets</artifactId>
            <version>${google.sheets.version}</version>
        </dependency>

Создадим два сервиса. GoogleConnection будет загружать клиентские данные из локального файла и хранить идентификационные данные после их получения.

GoogleConnectionService.java

@Service
public class GoogleConnectionService implements GoogleConnection {
    private static final String CLIENT_SECRETS = "/client_secrets.json";
    // ..
    @Override
    public GoogleClientSecrets getClientSecrets() {
        if (clientSecrets == null) {
            try {
                // load client secrets
                InputStreamReader clientSecretsReader = new InputStreamReader(getSecretFile());
                clientSecrets = GoogleClientSecrets.load(Global.JSON_FACTORY, clientSecretsReader);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return clientSecrets;
    }
    @Override
    public Credential getCredentials() {
        return credential;
    }
  // ..
}

А GoogleSheets будет выполнять основную работу — считывать табличные данные.

GoogleSheetsService.java

@Service
public class GoogleSheetsService implements GoogleSheets {
    private Sheets sheetsService = null;
 
    @Override
    public List<List<Object>> readTable(GoogleConnection connection) throws IOException {
        Sheets service = getSheetsService(connection);
        return readTable(service, spreadsheetId, sheetName);
    }
 
    private Sheets getSheetsService(GoogleConnection gc) throws IOException {
        if (this.sheetsService == null) {
            this.sheetsService = new Sheets.Builder(Global.HTTP_TRANSPORT, Global.JSON_FACTORY, gc.getCredentials())
                    .setApplicationName(appName).build();
        }
        return this.sheetsService;
    }
}

Всю последовательность операций распределим между тремя контроллерами. Контроллер для авторизации.

GoogleAuthorizationController.java

@RestController
public class GoogleAuthorizationController {
    @Autowired
    private GoogleConnectionService connection;
 
    @RequestMapping(value = "/ask", method = RequestMethod.GET)
    public void ask(HttpServletResponse response) throws IOException {
        // Step 1: Authorize --> ask for auth code
        String url = new GoogleAuthorizationCodeRequestUrl(connection.getClientSecrets(), connection.getRedirectUrl(), Global.SCOPES).setApprovalPrompt("force").build();
        response.sendRedirect(url);
    }
}

Результатом его работы будет редирект на google для логина.

вход в google

Затем запрос на доступ google приложения к пользовательским таблицам:

запрос на доступ приложения к данным пользователя

В случае успешной авторизации контроллер обратной связи обменяет код на токены и перенаправит на исходный url. За сам обмен отвечает класс GoogleAuthorizationCodeTokenRequest.

GoogleCallbackController.java

@RestController
public class GoogleCallbackController {
    @Autowired
    private GoogleConnectionService connection;
 
    @RequestMapping(value = "/oauth2callback", method = RequestMethod.GET)
    public void callback(@RequestParam("code") String code, HttpServletResponse response) throws IOException {
        // Step 2: Exchange code --> access tocken
        if (connection.exchangeCode(code)) {
            response.sendRedirect(connection.getSourceUrl());
        } else {
            response.sendRedirect("/error");
        }
    }
}

И, собственно, рабочий контроллер, реализующий чтение табличных данных.

GoogleSheetController.java

@RestController
public class GoogleSheetController {
    @Autowired
    private GoogleConnection connection;
    @Autowired
    private GoogleSheets sheetsService;
 
    @RequestMapping(value = "/api/sheet", method = RequestMethod.GET)
    public ResponseEntity<List<List<Object>>> read(HttpServletResponse response) throws IOException {
        List<List<Object>> responseBody = sheetsService.readTable(connection);
        return new ResponseEntity<List<List<Object>>>(responseBody, HttpStatus.OK);
    }
}

Также нам потребуется интерцептор, чтобы невозможно было обратиться к рабочему контроллеру без аутентификации.

GoogleSheetsInterceptor.java

public class GoogleSheetsInterceptor implements HandlerInterceptor {
        // ..
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object obj) throws Exception {
        if (connection.getCredentials() == null) {
            connection.setSourceUrl(request.getRequestURI());
            response.sendRedirect("/ask");
            return false;
        }
        return true;
    }
}

По сравнению с API v3, каждая строка представляет собой список объектов.

    private List<List<Object>> readTable(Sheets service, String spreadsheetId, String sheetName) throws IOException {
        ValueRange table = service.spreadsheets().values().get(spreadsheetId, sheetName).execute();
        List<List<Object>> values = table.getValues();
        return values;
    }

Используя нотацию A1 считываем постранично, а не блоками. Для этого добавим параметра id страницы и имя вкладки в настройки приложения.

google.spreadsheet.id=..
google.spreadsheet.sheet.name=..

Запускаем. Проверяем.

В результате должно сложиться представление о том, какие классы Google API служат для подключения к своим сервисам по OAuth 2.0 и как их можно использовать из web-приложения.

Spring sso приложение

Код приложения, отвечающий за работу с OAuth2 можно упростить за счет spring. Для этого подключим Spring Security OAuth.

    <dependency>
        <groupId>org.springframework.security.oauth</groupId>
        <artifactId>spring-security-oauth2</artifactId>
    </dependency>

Это нам позволит спрятать рутинные операции OAuth2 под капот и защитит наше приложение.

Перенесем пользовательские секреты в application.properties.

security.oauth2.client.client-id=Enter Client Id
security.oauth2.client.client-secret=Enter Client Secret
security.oauth2.client.accessTokenUri=https://accounts.google.com/o/oauth2/token
security.oauth2.client.userAuthorizationUri=https://accounts.google.com/o/oauth2/auth
security.oauth2.client.scope=openid,profile,https://www.googleapis.com/auth/spreadsheets
security.oauth2.resource.user-info-uri=https://www.googleapis.com/oauth2/v3/userinfo

Подключив security к проекту, мы уже активировали базовую аутентификацию. Заменим её на что-то более подходящее.

Раз уж мы считываем данные из Google, пусть он и занимается аутентификацией пользователей для для нашего приложения. Для этого добавим всего лишь одну аннотацию @EnableOAuth2Sso.

@EnableOAuth2Sso
@SpringBootApplication
public class Application  { //.. }

Будет создана и настроена точка аутентификации SSO. Нет необходимости переопределять WebSecurityConfigurerAdapter. Нам остаётся только задать пару параметров в конфигурации.

security.ignored=/
security.basic.enabled=false
security.oauth2.sso.login-path=/oauth2callback

В данном случае login-path должен соответствовать URI для редиректа, заданный в google проекте. А параметр scope должен содержать значение profile в том числе.

Дополнительные контроллеры и интерцептор больше не нужны. Теперь их работу будет выполнять spring.

Изменим класс GoogleConnection. Credential's мы будем создавать используя авторизационный код, сохраненный после аутентификации в OAuth2 контексте. А клиентские данные будем брать из конфига приложения.

GoogleConnectionService.java

@Service
public class GoogleConnectionService implements GoogleConnection {
    @Autowired
    private OAuth2ClientContext oAuth2ClientContext;
    private GoogleCredential googleCredentials = null;
        // ..
    @Override
    public Credential getCredentials() {
        if (googleCredentials == null) {
            googleCredentials = new GoogleCredential.Builder()
                    .setTransport(Global.HTTP_TRANSPORT)
                    .setJsonFactory(Global.JSON_FACTORY)
                    .setClientSecrets(clientId, clientSecret)
                    .build()
                    .setAccessToken(response.getAccessToken())
                    .setFromTokenResponse(oAuth2ClientContext
                        .getAccessToken().getValue());
        }
        return googleCredentials;
    }
}

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

На этом все. Рабочие исходники есть на github'e. Разные подходы — по разным веткам.

Автор: cane

Источник

Поделиться

* - обязательные к заполнению поля