Статьи

OAuth в безголовых приложениях

OAuth — замечательный стандарт: он позволяет пользователям предоставлять сторонним службам разрешения на использование своих учетных записей на веб-сайте; но это работает, не заставляя их делиться своим паролем, как это сделал бы фишинговый сайт.

Типичное использование OAuth для доступа к Facebook, Twitter или Google+ Api (социальные сети имеют много данных для обмена). Отлично работает:

  1. Генерируется URL на сайте socialnetwork.com , который загружает пользователь. URL обратного вызова, указывающий на ваше приложение, прикрепляется как параметр GET.
  2. Поскольку пользователь вошел в систему (или может войти в систему с помощью простой формы) на socialnetwork.com, токен генерируется случайным образом и авторизуется . В случае, если требуются некоторые разрешения, пользователю предлагается подтвердить.
  3. Пользователь перенаправляется обратно в ваше приложение с токеном, который теперь вы можете использовать для отправки запросов. Вы никогда не узнаете пароль пользователя.

Полное объяснение OAuth выходит за рамки этого поста; сегодня я хочу решить, как использовать OAuth внутри безголовых приложений, таких как Jenkins, PHPUnit или Ant.

Безголовое приложение?

Безголовое приложение не имеет реального пользовательского интерфейса и может работать в фоновом режиме в течение нескольких дней. Рассмотрим, например, Jenkins, выполняющих сборки для непрерывной интеграции; сервер Selenium; или сканер, загружающий веб-страницы весь день на сервере.

Дело в том, что в автономном приложении вы не можете отправлять пользователя (который может быть вами) в браузер для утверждения при каждой перезагрузке (например, потому, что процесс выполняется на компьютере CI). И, как правило, социальная сеть не может перенаправить пользователя в автономное приложение с заголовком HTTP Location.

Однако в тех случаях, когда нам интересен пользователь, нужен для того, чтобы делать запросы к API. Большинство социальных сетей позволяет вам совершать различные вызовы Api только после входа в систему с помощью пользователя.

Таким образом, мы должны разделить наше приложение на две части : одну (маленькую), чья задача — получить токен аутентификации, и одну — использовать его как обычно.

Получение токена

Мы предполагаем (см. Соответствующий раздел в конце статьи), что долгоживущие токены доступны для авторизации. LinkedIn работает следующим образом, и я использовал своего собственного пользователя для авторизации, чтобы сделать вызовы API для данных групп.

Я интегрировал пример авторизации из Scribe , самой простой библиотеки Java для OAuth. Вы загружаете URL-адрес, предоставленный Scribe, в браузер, следуйте процедуре авторизации для конкретного сайта, а затем вставляете обратно параметр верификатора в Scribe.

Однако вместо того, чтобы использовать его немедленно, я распечатал токен и сохранил его в файле .properties, который Git игнорирует при конфигурировании, чтобы избежать его публикации в хранилище исходного кода. Осторожно, коммит остается в хранилище навсегда!

import java.io.IOException;
import java.util.Properties;
import java.util.Scanner;

import org.scribe.builder.ServiceBuilder;
import org.scribe.builder.api.LinkedInApi;
import org.scribe.model.OAuthRequest;
import org.scribe.model.Response;
import org.scribe.model.Token;
import org.scribe.model.Verb;
import org.scribe.model.Verifier;
import org.scribe.oauth.OAuthService;

public class LinkedInConfigurator {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
	    try {
		Properties properties = LinkedInDefault.getConfiguration();
		
		OAuthService service = new ServiceBuilder()
	        .provider(LinkedInApi.class)
	        .apiKey(properties.getProperty("apiKey"))
	        .apiSecret(properties.getProperty("apiSecret"))
	        .callback("http://localhost")
	        .build();
			
		Scanner in = new Scanner(System.in);
	
		System.out.println("Fetching the Request Token...");
		Token requestToken = service.getRequestToken();
		System.out.println("Got the Request Token!");
		System.out.println();
	
		System.out.println("Now go and authorize Scribe here:");
		System.out.println(service.getAuthorizationUrl(requestToken));
		System.out.println("And paste the verifier here");
		System.out.print(">>");
		Verifier verifier = new Verifier(in.nextLine());
		System.out.println();
		System.out.println("Trading the Request Token for an Access Token...");
		Token accessToken = service.getAccessToken(requestToken, verifier);
		System.out.println("The access token is [accessToken, accessSecret]: " + accessToken);
	    } catch (IOException e) {
		e.printStackTrace();
	    }
	}

}

В моем случае процесс сохранения выполняется вручную, но вы можете легко сохранить токен, где хотите.

Использование токена

Чтобы использовать токен доступа, снова создайте экземпляр Scribe OAuthService вместе с токеном: вы должны передать конструктору две информации, полученные при его сбросе (значение токена и секрет).

Теперь вы можете создавать запросы и передавать их службе вместе с токеном для подписи. Безголовое приложение получает все разрешения, которые пользователь имеет в API.

import static org.junit.Assert.assertTrue;

import org.scribe.model.OAuthRequest;
import org.scribe.model.Response;
import org.scribe.model.Token;
import org.scribe.model.Verb;
import org.scribe.oauth.OAuthService;

public class ScribeLinkedInService implements LinkedInService {

	private OAuthService oauthService;
	private Token accessToken;

	public ScribeLinkedInService(OAuthService oauthService, Token accessToken) {
	    this.oauthService = oauthService;
	    this.accessToken = accessToken;
	}

	@Override
	public String getLastPostsResponse(int groupId, int numberOfPosts,
			long timestampToStartFrom) {
	    OAuthRequest request = new OAuthRequest(Verb.GET, "http://api.linkedin.com/v1/groups/" + groupId + "/posts?count=" + numberOfPosts + "&modified-since=" + timestampToStartFrom);
	    oauthService.signRequest(accessToken, request);
	    Response response = request.send();
	    return response.getBody();
	}

}

Условия обслуживания и срок действия

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

LinkedIn — это то место, где я протестировал этот подход, и он прямо сказал, что, если пользователь указывает это, токены являются долговечными и не имеют срока действия до тех пор, пока не будут явно отменены. На самом деле я все еще использую один из полученных на прошлой неделе для проведения интеграционных тестов.

Facebook по умолчанию предоставляет вам параметр (в URL перенаправления) вместе с токеном, который сообщает, сколько секунд токен будет длиться. Однако если вы спросите пользователя оразрешении offline_access впараметре scope диалогового окна OAuth, токен будет иметь бесконечное время истечения.

Не совсем безопасно для пользователя навсегда отказаться от доступа к своей учетной записи к приложению, но я полагаю, что вы используете своего собственного пользователя (или фиктивного), как я с LinkedIn. Тогда вы уже сохраняете свой пароль в браузере, не так ли?

В настоящее время Twitter не имеет маркеров доступа , если ваше приложение не приостановлено или пользователь не отклонил его со своей страницы настроек. Более того, он предлагает широкую публичную часть своего API, где вам не требуется аутентифицированный пользователь для выполнения запросов; эти трюки не могут быть необходимы.

FourSquare не истекает токены , но может сделать это в будущем.