Содержание


Использование клиентов OAuth 2.0 в Java-программировании

Часть 1. Передача реквизитов доступа владельца ресурса

Серия контента:

Этот контент является частью # из серии # статей: Использование клиентов OAuth 2.0 в Java-программировании

Следите за выходом новых статей этой серии.

Этот контент является частью серии:Использование клиентов OAuth 2.0 в Java-программировании

Следите за выходом новых статей этой серии.

Обзор

OAuth – это открытый стандарт авторизации, позволяющий клиентам получать доступ к защищенным ресурсам сервера от имени владельца ресурса. Владельцем ресурса может быть другой клиент или конечный пользователь. OAuth также помогает конечным пользователям разрешать третьим лицам доступ к своим ресурсам на сервере без необходимости передавать собственные регистрационные данные, такие как имя пользователя и пароль. Эта серия статей посвящена системе авторизации OAuth 2.0, описанной в документе RFC 6749. Полная система авторизации OAuth 2.0, описанная в RFC 6749, находится на веб-сайте Internet Engineering Task Force (см. раздел Ресурсы).

Грант авторизации

Грант авторизации – это реквизит доступа, служащий разрешением владельца ресурса на авторизацию, который можно использовать для доступа к защищенному ресурсу. Это разрешение используется клиентом для получения ключа доступа. В конечном итоге ключ доступа отправляется вместе с запросом на доступ к защищенному ресурсу. В OAuth 2.0 определены гранты четырех типов:

  1. Код авторизации
  2. Неявный
  3. Реквизиты доступа владельца ресурса
  4. Реквизиты клиента

Это первая из четырех частей, в которых мы разберем четыре способа реализации клиента OAuth 2.0 на языке программировании Java™ с использованием каждого из перечисленных выше типов грантов. В этой первой части я покажу, как реализовать грант реквизитов доступа владельца ресурса. В этой статье приводится подробное описание гранта и рассматривается пример клиентского кода, который можно использовать для связи с любым OAuth 2.0-совместимым сервером, поддерживающим этот грант. После прочтения статьи читатель должен иметь полное представление о реализации клиента и быть готовым к загрузке примера клиентского кода для самостоятельного тестирования.

Грант реквизитов доступа владельца ресурса

Грант реквизитов доступа владельца ресурса используется тогда, когда владелец ресурса в высокой степени доверяет клиенту. Этот тип гранта подходит клиенту, который может получить имя пользователя и пароль владельца ресурса. Он удобнее всего для корпоративных клиентов, использующих базовую HTTP- или дайджест-аутентификацию, желающих перейти на Oauth. Это можно сделать с помощью имеющихся у них реквизитов для создания ключа доступа.

Например, Salesforce.com добавил механизм авторизации OAuth 2.0 в свою существующую инфраструктуру. Чтобы перейти на эту схему авторизации, клиентам удобнее всего использовать грант реквизитов доступа владельца ресурса, так как в этом случае они смогут получить ключ доступа по своим регистрационным данным, таким как имя пользователя и пароль.

Рисунок 1. Схема гранта реквизитов доступа владельца ресурса
Схема передачи реквизитов доступа владельца ресурса
Схема передачи реквизитов доступа владельца ресурса

Схема, приведенная на рисунке 1, состоит из следующих действий.

  1. Владелец ресурса передает доверенному клиенту OAuth 2.0 свои имя пользователя и пароль.
  2. Клиент OAuth 2.0 делает запрос на получение ключа доступа к соответствующей конечной точке сервера авторизации, включая в него реквизиты, полученные от владельца ресурса. Принимая запрос, клиент OAuth 2.0 проходит аутентификацию на сервере авторизации с использованием реквизитов, предоставленных сервером авторизации.
  3. Сервер авторизации проверяет подлинность клиента OAuth 2.0 и реквизиты владельца ресурса. Если они действительны, сервер авторизации выдает ключ доступа.

Запрос ключа доступа

Запрос ключа доступа соответствует второму шагу, показанному на рисунке 1.

Клиент делает запрос к конечной точке выдачи ключа (серверу авторизации), передавая следующие параметры в формате application/x-www-form-urlencoded.

  • grant_type: ОБЯЗАТЕЛЬНЫЙ. Должен иметь значение password.
  • username: ОБЯЗАТЕЛЬНЫЙ. Имя пользователя владельца ресурса.
  • password: ОБЯЗАТЕЛЬНЫЙ. Пароль владельца ресурса.
  • scope: ФАКУЛЬТАТИВНЫЙ. Область запроса доступа.

Если это клиент конфиденциального типа или клиенту были выданы реквизиты доступа (или назначены другие требования проверки подлинности), то он должен пройти проверку подлинности на сервере авторизации. Например, клиент делает следующий HTTP-запрос с использованием средств безопасности транспортного уровня.

Листинг 1. Проверка подлинности на сервере авторизации
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=password&username=varun&password=ab32vr

Ответ на запрос ключа доступа

Ответ на запрос ключа доступа соответствует шагу C на рисунке 1. Если запрос ключа доступа действителен и авторизован, то сервер авторизации возвращает ключ доступа и необязательный ключ обновления. Пример успешного ответа приведен в листинге 2.

Листинг 2. Положительный ответ на запрос ключа доступа
    HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
"access_token":"2YotnFZFEjr1zCsicMWpAA",
"token_type":"example",
"expires_in":3600,
"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
"example_parameter":"example_value"
}

Если запрос недопустимый или несанкционированный, то сервер авторизации возвращает код с соответствующим сообщением об ошибке.

Установка

Прилагаемый пример клиента OAuth 2.0 представляет собой проект Java, который можно импортировать в среду Eclipse. Вам потребуется загрузить в папку lib проекта Java готовые JAR-файлы зависимостей.

JAR-файлы зависимостей

В проекте используются следующие JAR-файлы:

  • commons-codec-1.6.jar
  • commons-logging-1.1.1.jar
  • httpclient-4.2.5.jar
  • httpclient-cache-4.2.5.jar
  • httpcore-4.2.4.jar
  • httpmime-4.2.5.jar
  • json-simple-1.1.1.jar

Первые шесть из них можно найти в JAR-файлах Http Components. См. раздел Другие файлы для загрузки, где даны ссылки на эти файлы и файл json-simple-1.1.1.jar. Скопируйте эти JAR-файлы в папку lib проекта Java, доступного для загрузки в конце статьи.

Предварительные требования

Чтобы настроить среду разработки и импортировать прилагаемый проект, загрузите IDE Eclipse для разработчиков Java EE. См. раздел Другие файлы для загрузки.

Клиент OAuth 2.0

Обсуждаемый здесь клиент OAuth 2.0 реализует грант реквизитов доступа владельца ресурса. В следующих статьях этой серии будут описаны остальные типы грантов и продолжено редактирование клиентского кода.

Входные параметры

Входные параметры передаются клиенту с помощью файла свойств Oauth2Client.config, присутствующего в примере кода клиента (см. раздел Загрузки).

  • scope: это необязательный параметр. Он определяет область запроса доступа. Ключ доступа, возвращенный сервером, будет обеспечивать доступ только к тем службам, которые входят в эту область.
  • grant_type: должен иметь значение "password", соответствующее гранту реквизитов доступа владельца ресурса.
  • username: имя пользователя, используемое для входа в систему сервера ресурсов.
  • password: пароль, используемый для входа в систему сервера ресурсов.
  • client_id: идентификатор клиента или потребителя, предоставляемый сервером ресурсов при регистрации приложения.
  • client_secret: секретный ключ клиента или потребителя, предоставляемый сервером ресурсов при регистрации приложения.
  • access_token: ключ доступа, возвращенный сервером авторизации в ответ на действительный и авторизованный запрос ключа доступа. В рамках этого запроса ваше имя пользователя и пароль заменяются ключом доступа.
  • refresh_token: необязательный параметр, который сервер авторизации может возвратить в ответ на запрос ключа доступа. Однако большинство конечных точек, таких как Salesforce, IBM Websphere® Application Server и IBM Datapower, не возвращает ключ обновления для гранта реквизитов доступа владельца ресурса. Поэтому в моей реализации клиента ключ обновления не рассматривается.
  • authenticatation_server_url: конечная точка ключа. Все запросы на предоставление и возобновление ключей доступа необходимо направлять на этот URL-адрес.
  • resource_server_url: URL-адрес сервера ресурсов, по которому нужно обращаться, чтобы получить доступ к защищенному ресурсу, передавая ему ключ доступа в заголовке авторизации.
Листинг 3. Код Oauth2Client
 Properties config = OAuthUtils.getClientConfigProps 	(OAuthConstants.CONFIG_FILE_PATH);
 String resourceServerUrl = config.getProperty(OAuthConstants.RESOURCE_SERVER_URL);      
 String username = config.getProperty(OAuthConstants.USERNAME);
 String password = config.getProperty(OAuthConstants.PASSWORD);
 String grantType = config.getProperty(OAuthConstants.GRANT_TYPE);
 String authenticationServerUrl = config
         .getProperty(OAuthConstants.AUTHENTICATION_SERVER_URL);

  if (!OAuthUtils.isValid(username)
    || !OAuthUtils.isValid(password)
    || !OAuthUtils.isValid(authenticationServerUrl)
    || !OAuthUtils.isValid(grantType)) {
  System.out
        .println("Please provide valid values for username, password,
                        authentication server url and grant type");
  System.exit(0);

   }
 if (!OAuthUtils.isValid(resourceServerUrl)) {
 // URL-адрес сервера ресурса недействителен.
 //Только извлечение ключа доступа
 System.out.println("Retrieving Access Token");
 OAuth2Details oauthDetails = OAuthUtils.createOAuthDetails(config);
 String accessToken = OAuthUtils.getAccessToken(oauthDetails);
 System.out
 .println("Successfully retrieved Access token
  for Password Grant: " + accessToken);
 }
 else {
 // Ответ от сервера ресурсов должен быть в формате Json, 
 //URL-кода или xml
 System.out.println("Resource endpoint url: " + resourceServerUrl);
 System.out.println("Attempting to retrieve protected resource");
 OAuthUtils.getProtectedResource(config);
      }

Код клиента, приведенный в листинге 3, считывает входные параметры, указанные в файле Oauth2Client.config. Действительные значения username, password, authentication server url и grant type обязательны. Если URL сервера ресурсов в файле конфигурации действителен, то клиент пытается получить защищенный ресурс, доступный по этому URL. В противном случае клиент только делает запрос ключа доступа к серверу авторизации и извлекает ключ доступа. В следующем разделе объясняется, как работает код, отвечающий за извлечение защищенного ресурса и ключа доступа.

Доступ к защищенному ресурсу

Код, приведенный в листинге 4, демонстрирует, как получить доступ к защищенному ресурсу, используя ключ доступа.

Листинг 4. Доступ к защищенному ресурсу
String resourceURL =
config.getProperty(OAuthConstants.RESOURCE_SERVER_URL);
OAuth2Details oauthDetails = createOAuthDetails(config);
HttpGet get = new HttpGet(resourceURL);
get.addHeader(OAuthConstants.AUTHORIZATION,
getAuthorizationHeaderForAccessToken(oauthDetails
.getAccessToken()));
DefaultHttpClient client = new DefaultHttpClient();
HttpResponse response = null;
int code = -1;
  try {
	response = client.execute(get);
	code = response.getStatusLine().getStatusCode();
	if (code >= 400) {
      // Ключ доступа недействителен или просрочен.
      // Восстановление ключа доступа
      System.out.println("Access token is invalid
      or expired. Regenerating access token....");
      String accessToken = getAccessToken(oauthDetails);
      if (isValid(accessToken)) {
	// Обновление ключа доступа
      // System.out.println("New access token: " + accessToken);
      oauthDetails.setAccessToken(accessToken);
      get.removeHeaders(OAuthConstants.AUTHORIZATION);
      get.addHeader(OAuthConstants.AUTHORIZATION,
      getAuthorizationHeaderForAccessToken(oauthDetails
	.getAccessToken()));
      get.releaseConnection();
      response = client.execute(get);
      code = response.getStatusLine().getStatusCode();
	if (code >= 400) {
	throw new RuntimeException("Could not
  access protected resource.
  Server returned http code: "+ code);

     }

   } else {
	throw new RuntimeException("Could not
       regenerate access token");
	}

 }

   handleResponse(response);

Примечание.

  • Этот метод заполняет компонент OauthDetails значениями из файла конфигурации.
  • Как подсказывает название, этот метод пытается получить защищенный ресурс с сервера ресурсов, так что здесь создается простой метод HttpGet.
  • Для аутентификации на сервере ресурсов нужно передать в заголовке авторизации ключ доступа.

    Пример: Authorization: Bearer accessTokenValue

  • Создайте компонент DefaultHttpClient, чтобы сделать запрос get к серверу ресурсов.
  • Если код ответа сервера ресурсов 403 или 401, то ключ доступа, используемый для аутентификации, недействителен или просрочен.
  • На следующем шаге выполняется регенерация ключа доступа (Листинг 5).
  • После успешной регенерации обновите значение ключа доступа в компоненте OauthDetails. Замените существующий заголовок авторизации в методе get новым значением ключа доступа.
  • Теперь сделайте другой запрос доступа к защищенному ресурсу.
  • Если ключ доступа действителен и URL-адрес сервера ресурсов правильный, вы увидите в окне консоли содержимое ответа.

Возобновление просроченного ключа доступа

Код, приведенный в листинге 5, управляет возобновлением просроченных ключей доступа.

Листинг 5. Возобновление просроченного ключа доступа
 HttpPost post = new HttpPost(
 oauthDetails.getAuthenticationServerUrl());
 String clientId = oauthDetails.getClientId();
 String clientSecret = oauthDetails.getClientSecret();
 String scope = oauthDetails.getScope();

 List<BasicNameValuePair> parametersBody =
 new ArrayList<BasicNameValuePair>();
 parametersBody.add(new BasicNameValuePair(OAuthConstants.GRANT_TYPE,
 oauthDetails.getGrantType()));
 parametersBody.add(new BasicNameValuePair(OAuthConstants.USERNAME,
 oauthDetails.getUsername()));
 parametersBody.add(new BasicNameValuePair(OAuthConstants.PASSWORD,
 oauthDetails.getPassword()));

 if (isValid(clientId)) {
	parametersBody.add(new BasicNameValuePair
      (OAuthConstants.CLIENT_ID,clientId));
  }
 if (isValid(clientSecret)) {
	parametersBody.add(new BasicNameValuePair(
	OAuthConstants.CLIENT_SECRET, clientSecret));
  }
 if (isValid(scope)) {
	parametersBody.add(new BasicNameValuePair
      (OAuthConstants.SCOPE,scope));
  }

 DefaultHttpClient client = new DefaultHttpClient();
 HttpResponse response = null;
 String accessToken = null;
   try {
	post.setEntity(new UrlEncodedFormEntity(parametersBody,
       HTTP.UTF_8));
	
	response = client.execute(post);
	int code = response.getStatusLine().getStatusCode();
	if (code >= 400) {
	System.out.println("Authorization
       server expects Basic authentication");
	// Добавление основного заголовка авторизации
	post.addHeader(
	OAuthConstants.AUTHORIZATION,
	getBasicAuthorizationHeader(oauthDetails.getUsername(),
	oauthDetails.getPassword()));
	System.out.println("Retry with login credentials");
	post.releaseConnection();
	response = client.execute(post);
	code = response.getStatusLine().getStatusCode();
	if (code >= 400) {
	System.out.println("Retry with client credentials");
	post.removeHeaders(OAuthConstants.AUTHORIZATION);
	post.addHeader(
	OAuthConstants.AUTHORIZATION,
       getBasicAuthorizationHeader(
	 oauthDetails.getClientId(),
	oauthDetails.getClientSecret()));
	post.releaseConnection();
	response = client.execute(post);
	code = response.getStatusLine().getStatusCode();
	if (code >= 400) {
	throw new RuntimeException(
	"Could not retrieve access token for user: "
	oauthDetails.getUsername());
	   }
        }
      }
	Map<String, String> map = handleResponse(response);
	accessToken = map.get(OAuthConstants.ACCESS_TOKEN);
	} catch (ClientProtocolException e) {
			// автоматически сгенерированный блок-ловушка TODO
			e.printStackTrace();
	} catch (IOException e) {
			// автоматически сгенерированный блок-ловушка TODO
			e.printStackTrace();
	}

	return accessToken;

Примечание.

  • Этот метод генерирует запрос на HttpPost и направляет его по URL-адресу сервера аутентификации.
  • В запросе Postпередаются параметры username, password и, при необходимости, scope в URL-коде.
  • Некоторые серверы авторизации требуют также отправлять в составе этого запроса параметры client_id и client_secret.
  • Если значения параметров client_id, client_secret и scope не равны null, они также передаются как часть полезной нагрузки.
  • Согласно системе авторизации OAuth 2.0, для проверки подлинности при запросе ключа доступа клиент должен установить заголовок авторизации со своими реквизитами доступа или другими учетными данными, предоставленными сервером. Но это – предмет реализации сервера авторизации. Клиентский код делает первоначальный запрос без добавления основного заголовка аутентификации. Если сервер возвращает несанкционированный ответ, то клиент впоследствии пытается пройти аутентификацию со своими реквизитами доступа.
  • OAuth 2.0 требует, чтобы ответ на запрос ключа доступа отправлялся в формате JSON. Но я добавил для гибкости дополнительные методы обработки ответа от сервера в формате xml или URL-кода.

Тестирование клиента

В этом разделе мы обсудим способы настройки OAuth 2.0-совместимой конечной точки и тестирования клиента через нее.

Регистрация с помощью Salesforce.com

Salesforce.com — это хороший способ передачи реквизитов доступа владельца ресурса. Если у пользователя есть учетные данные Salesforce.com и он хочет перевести свой клиент на аутентификацию OAuth 2.0, то для получения реквизитов доступа клиента ему достаточно зарегистрировать свое приложение в Salesforce. Эти реквизиты клиента вместе с его существующими учетными данными можно использовать для получения ключа доступа от сервера авторизации.

  1. Если вы еще не зарегистрировались в Salesforce.com, сделайте это сейчас. См. раздел Ресурсы
  2. Нажмите на ссылку Login и выберите Sign up for free.
  3. Выполните регистрацию и получите реквизиты.
  4. Помимо имени пользователя и пароля, вы также получите ключ безопасности. Пароль, передаваемый в запросе ключа доступа, должен состоять из вашего пароля и ключа безопасности. (Например: password12312123).
  5. См. ссылку в разделе Ресурсы на полезную статью о том, как создать приложение в salesforce.com.

Запуск клиента

Выполнив регистрацию в Salesforce.com, вы готовы к тестированию клиента и получению защищаемой информации от его сервера.

  • Импортируйте проект Java, обсуждаемый в этой статье, в рабочее пространство eclipse (см. раздел Ресурсы).
  • Загрузите и скопируйте JAR-файлы зависимостей в папку lib проекта (см. раздел Другие файлы для загрузки).
  • Откройте файл resources/com/ibm/oauth/Oauth2Client.config и введите значения username, password (добавьте ключ безопасности), client_id, client_secret и authorization server URL.
  • Откройте файл Oauth2Client.java и выполните его.

Получение ключа доступа

Вы должны увидеть в окне консоли следующий результат.

Retrieving Access Token
encodedBytes dmVybi5vamhhQGdtYWlsL.......

********** Response Received **********
  instance_url = https://ap1.salesforce.com
  issued_at = 1380106995639
  signature = LtMjTrmoBbvVfZ6+qT5Un1UioHaV9KIOK7ayQTmJzCg=
  id = https://login.salesforce.com/id/00D90000000mQaYEAU/00590000001HCB7AAO
  access_token = 00D90000000mQaY!AQ8AQEn0rLDMvxrP9WgY3Blc.......
Successfully retrieved Access token for Password Grant: 00D90000000mQaY!AQ8AQEn0rLDMvxrP9WgY3Bl......

Получение сведений о пользователе от Salesforce.com

Имея ключ доступа и идентификатор, можно делать запросы в Salesforce.com для получения доступа к сведениями о своей учетной записи методом аутентификации OAuth 2.0.

  • Отредактируйте файл Oauth2Client.confg с помощью ключа доступа и укажите в качестве URL сервера ресурсов значение id, полученное в ответе.
  • Снова выполните Oauth2Client.java.

Результат

Вы должны увидеть в окне консоли результат, подобный следующему.

Листинг 6. Результат
Resource endpoint URL: https://login.salesforce.com/id/00D90000000mQaYEAU/00590000001HCB7AAO
Attempting to retrieve protected resource
********** Response Received **********
photos = {"thumbnail":"https:\/\/c.ap1.content.force.com\/profilephoto\/005\/T","picture":"https:\/\
/c.ap1.content.force.com\/profilephoto\/005\/F"}
urls =
{"enterprise":"https:\/\/ap1.salesforce.com\/services\/Soap\/c\/{version}\/00D90000000mQaY","sobjects":
"https:\/\/ap1.salesforce.com\/services\/data\/v{version}\/sobjects\/","partner":"https:\/\
/ap1.salesforce.com\/services\/Soap\/u\/{version}\/00D90000000mQaY","search":"https:\/\
/ap1.salesforce.com\/services\/data\/v{version}\/search\/","query":"https:\/\/ap1.salesforce.com\
/services\/data\/v{version}\/query\/","users":"https:\/\/ap1.salesforce.com\/services\/data\/v{version}\
/chatter\/users","profile":"https:\/\/ap1.salesforce.com\/00590000001HCB7AAO","metadata":"https:\/\
/ap1.salesforce.com\/services\/Soap\/m\/{version}\/00D90000000mQaY","rest":"https:\/\/ap1.salesforce.com\
/services\/data\/v{version}\/","groups":"https:\/\/ap1.salesforce.com\/services\/data\/v{version}\
/chatter\/groups","feeds":"https:\/\/ap1.salesforce.com\/services\/data\/v{version}\/chatter\/feeds",
"recent":"https:\/\/ap1.salesforce.com\/services\/data\/v{version}\/recent\/","feed_items":"https:\/
\/ap1.salesforce.com\/services\/data\/v{version}\/chatter\/feed-items"}
  asserted_user = true
  active = true
  organization_id = 00D90000000mQaYEAU
  nick_name = vern.ojha1....
  display_name = varun ojha
  user_type = STANDARD
  user_id = ***********
  status = {"body":null,"created_date":null}
  last_name = ojha
  username = vern.ojha.....
  utcOffset = -28800000
  language = en_US
  locale = en_US
  first_name = varun
  last_modified_date = 2013-06-04T07:43:42.000+0000
  id = https://login.salesforce.com/id/00D90000000mQaYEAU/00590000001HCB7AAO
  email = vern.ojha@gmail.com

Как видите, вы успешно получили сведения о пользователе методом аутентификации OAuth 2.0. Когда указанный в файле конфигурации срок действия ключа доступа истечет, клиент автоматически обновит его и будет использовать для извлечения защищенного ресурса, доступного по URL-адресу сервера ресурсов.

Тестирование клиента с конечными точками IBM

Этот клиент успешно протестирован с OAuth 2.0-совместимыми конечными точками IBM, а именно, IBM WebSphere Application Server и IBM Datapower. См. ссылку в разделе Ресурсы на документ «Практическое применение OAuth: как подключить службу OAuth к WebSphere Application Server» – очень хороший материал по настройке OAuth 2.0 на сервере приложений WebSphere.

После настройки OAuth 2.0 на сервере приложений клиенту требуются те же входные данные, что и для Salesforce.com.

Заключение

Эта первая часть серии статей учит основам передачи реквизитов доступа владельца ресурса. Она демонстрирует, как написать на языке Java универсальный клиент OAuth 2.0, который устанавливает соединение с несколькими OAuth 2.0-совместимыми конечными точками и получает от них защищенные ресурсы. К статье прилагается пример клиента в виде проекта Java, чтобы читатель мог легко импортировать его в рабочее пространство Eclipse и приступить к тестированию. В последующих частях этой серии статей мы опишем оставшиеся три типа грантов, имеющихся в системе авторизации OAuth 2.0. Мы отредактируем код клиента для отражения этих типов грантов, а также добавим такие функции, как публикация на сервере ресурсов и обработка SSL.


Ресурсы для скачивания


Похожие темы

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Технология Java
ArticleID=1011923
ArticleTitle=Использование клиентов OAuth 2.0 в Java-программировании: Часть 1. Передача реквизитов доступа владельца ресурса
publish-date=07272015