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

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

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

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

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

На выходе должен получиться файл учетных данных client_secrets.json. Готовый файл впоследствии необходимо разместить в ресурсах своего проекта.
Сценарии для авторизации
Идем дальше. Google Sheets API v4 поддерживает различные сценарии для авторизации с использованием авторизационного кода:
- Для web-серверных приложений. Необходимо реализовать пару определеных сервлетов и добавить их в свой web.xml
- Служебные учетные записи. Служебные учетные записи заводятся на google и предоставляют клиентам доступ к своим ресурсам, а не к ресурсам пользователя.
- Для установленных приложений. Пример работы с API из консольного приложения.
- Для клиентских приложений. В браузере формируем запрос на получение кода доступа, который потом можно обменять на токены.
- Для android.
Для нас подходят сценарии для 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 будет загружать клиентские данные из локального файла и хранить идентификационные данные после их получения.
@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 будет выполнять основную работу — считывать табличные данные.
@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; } }
Всю последовательность операций распределим между тремя контроллерами. Контроллер для авторизации.
@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 приложения к пользовательским таблицам:

В случае успешной авторизации контроллер обратной связи обменяет код на токены и перенаправит на исходный url. За сам обмен отвечает класс GoogleAuthorizationCodeTokenRequest.
@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"); } } }
И, собственно, рабочий контроллер, реализующий чтение табличных данных.
@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); } }
Также нам потребуется интерцептор, чтобы невозможно было обратиться к рабочему контроллеру без аутентификации.
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 контексте. А клиентские данные будем брать из конфига приложения.
@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. Разные подходы — по разным веткам.

