Содержание


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

Часть 3. Передача кода авторизации

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

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

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

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

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

Обзор

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

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

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

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

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

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

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

Грант кода авторизации иллюстрируется на рисунке 1.

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

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

  • (A) Клиент (обычно веб-приложение) инициирует процесс, направляя агент пользователя владельца ресурса (обычно веб-браузер) в конечную точку авторизации. Запрос клиента содержит идентификатор клиента, область действия запроса, локальное состояние и URI переадресации. После предоставления доступа (или отказа) сервер авторизации возвращает агент пользователя (обычно веб-браузер) по URI переадресации.
  • (B) Владелец ресурса проходит проверку подлинности на сервере авторизации посредством агента пользователя и предоставляет клиенту доступ или отклоняет запрос.
  • (C) Если владелец ресурса предоставляет доступ, то сервер авторизации перенаправляет агент пользователя (обычно веб-браузер) назад к клиенту, используя предоставленный ранее (в запросе или при регистрации клиента) URI переадресации. URI переадресации включает в себя код авторизации и любые сведения о локальном состоянии, предоставленное клиентом.
  • (D) Клиент делает запрос ключа доступа из конечной точки выдачи ключей сервера авторизации, передавая код авторизации, полученный на предыдущем шаге. Делая запрос, клиент проходит проверку подлинности на сервере авторизации, используя реквизиты клиента. Клиент также включает в запрос используемый URI переадресации, чтобы получить код авторизации для проверки.
  • (E) Сервер авторизации выполняет проверку подлинности клиента. Он проверяет код авторизации и гарантирует, что полученный URI переадресации соответствует тому, что использовался для переадресации клиента на шаге (C). Если проверка проходит успешно, то сервер авторизации выдает ключ доступа и, при необходимости, ключ обновления – если запрашивается доступ в автономном режиме.

Запрос кода авторизации

Запрос кода авторизации соответствует шагам (А) и (B), см. Рисунок 1. На шаге (A) клиент делает запрос к серверу авторизации в формате application/x-www-form-urlencoded, как показано в листинге 1.

Листинг 1. Пример запроса кода авторизации
GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
Host: server.example.com

Запрос должен содержать следующие параметры:

  • response_type: обязательный. Должен иметь значение code.
  • client_id: обязательный. Идентификатор клиента.
  • redirect_uri: обязательный. Используется для переадресации агента пользователя.
  • scope: факультативный. Область запроса доступа.
  • state: факультативный. Для сохранения состояния между запросом и обратным вызовом.

После проверки запроса сервером авторизации тот возвращает клиенту ответ с кодом переадресации HTTP 302. Ответ также содержит URI переадресации в заголовке http Location. На шаге (B) клиент должен переадресовать агент пользователя (обычно веб-браузер) по этому URI. Этот URI переадресации обычно указывает на страницу входа, где владелец ресурса может войти со своими полномочиями и предоставить доступ по запросу клиента или отказать в нем.

Ответный код авторизации

Ответный код авторизации показан на шаге (C) рисунка 1. Если владелец ресурса посылает запрос доступа, то сервер авторизации выдает код авторизации. Сервер авторизации перенаправляет агент пользователя по URI переадресации, переданный в рамках запроса на шаге (A), и включает код авторизации в запрос URI переадресации в формате application/x-www-form-urlencoded.

URI имеет следующие параметры:

  • Code: обязательный. Код авторизации, созданный сервером авторизации. Этот код носит временный характер, и его срок быстро истекает. Клиент не должен использовать код авторизации более одного раза. Любые дальнейшие запросы с использованием этого кода должны отклоняться сервером авторизации. Код авторизации связан с идентификатором клиента и URI переадресации.
  • State: обязательный. Если в запросе кода авторизации клиента присутствовал параметр state, то этот параметр должен иметь точное значение, полученное от клиента.

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

Этот запрос соответствует шагу (D) на рисунке 1. Клиент делает запрос к конечной точке (сервер авторизации) в формате application/x-www-form-urlencoded, как показано в листинге 2.

Листинг 2. Пример запроса ключа доступа
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

             grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
             &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom&client_id=c342

Запрос ключа доступа должен иметь следующие параметры:

  • grant_type: обязательный. Должен иметь значение authorization_code.
  • client_id: обязательный. Идентификатор клиента.
  • client_secret: факультативный. Секретный ключ. Для проверки подлинности на сервере авторизации.
  • code: обязательный. Код авторизации, полученный от сервера.
  • redirect_uri: обязательный. Тот же, что и на шаге (A).

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

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

Этот ответ соответствует шагу (Е) на рисунке 1. Если запрос ключа доступа действителен и авторизован, то сервер авторизации возвращает ключ доступа в ответе на запрос ключа доступа. Пример успешного ответа показан в листинге 3.

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

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

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

Запрос ключа обновления

Это необязательный шаг, который используется, если клиент запросил доступ в автономном режиме и включил в запрос ключа доступа параметр refresh_token. Ключ доступа – это временный ключ, срок действия которого обычно истекает через час. По истечении срока действия ключа доступа клиент должен повторить процесс проверки подлинности, а владелец ресурса – войти в систему и снова предоставить клиенту разрешение на запрос ключа доступа.

Если клиенту необходимо обновить ключи доступа, когда владелец ресурса не работает с браузером, он использует доступ в автономном режиме. Клиент может запросить доступ в автономном режиме во время выполнения первого запроса кода авторизации (см. шаг (A)). По этой схеме сервер авторизации, кроме ключа доступа, возвращает ключ обновления. Ключ обновления — это долгоживущий ключ, который не имеет срока действия и может быть только явно отозван владельцем ресурса. Каждый раз, когда истекает срок действия ключа доступа, клиент может использовать ключ обновления для восстановления ключа доступа без необходимости владельцу ресурса входить в систему и разрешать доступ.

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

Листинг 4. Запрос к конечной точке
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA

Параметры запроса определяются следующим образом:

  • grant_type: обязательный. Должен иметь значение refresh_token.
  • refresh_token: обязательный. Извлекается ранее из запроса ключа доступа.
  • scope: факультативный. Область запроса доступа.

Сервер авторизации проверяет ключ обновления и выдает новый ключ доступа.

Ответ на запрос ключа обновления

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

Листинг 5. Ответ на запрос ключа обновления
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
  "access_token":"2YotnFZFEjr1zCsicMWpAA",
  "token_type":"Bearer",
  "expires_in":3600,
  "example_parameter":"example_value"
}

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

Установка

Пример клиента Outh2.0 представляет собой динамический веб-проект. Файл .war проекта и исходный код можно загрузить по ссылке в разделе Загрузки. Импортируйте файл .war в среду Eclipse.

Предварительные замечания

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

Для запуска веб-клиента OAuth 2.0 нужен контейнер Apache Tomcat JSP/Servlet. Tomcat можно загрузить со страницы загрузки Apache Tomcat.

Зависимости

Проект кода клиента зависит от следующих JAR-файлов:

  1. commons-codec-1.6.jar
  2. commons-logging-1.1.1.jar
  3. httpclient-4.2.5.jar
  4. httpclient-cache-4.2.5.jar
  5. httpcore-4.2.4.jar
  6. httpmime-4.2.5.jar
  7. json-simple-1.1.1.jar

JAR-файлы, упомянутые в пунктах 1-6, находятся в файле JAR HttpComponents, который можно загрузить со страницы загрузок HttpComponents. Файл json-simple-1.1.1.jar можно загрузить из проекта JSON Simple. Скопируйте эти jar-файлы в папку lib каталога установки Apache Tomcat. Некоторые из необходимых файлов JAR, возможно, уже присутствуют в Tomcat по умолчанию, так что нужно скопировать только недостающие.

Импорт файла .war проекта в Eclipse

После установки Eclipse и Apache Tomcat нужно импортировать файл .war из раздела Загрузки. Для этого выполните следующие простые инструкции, приведенные на веб-сайте Eclipse в разделе Импорт файлов веб архива (WAR).

Во время импорта файла .war в Eclipse должна поддерживаться связь сервера Tomcat с вашим проектом. Для завершения настройки нужно выбрать версию Tomcat и указать путь к корневой папке установки Tomcat.

Более подробные инструкции по установлению связи сервера Tomcat с проектом можно получить в разделе Справки Создание сервера Tomcat.

После успешного импорта файла .war в рабочее пространство Eclipse вы найдете исходный код в папке Java Resources/srcc в иерархии проекта.

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

Когда файл успешно импортирован .war и Tomcat настроен с необходимыми JAR-файлами, можно запустить клиент, щелкнув правой кнопкой мыши на имени проекта OAuth2.0_AuthCode и выбрав Run As и Run on Server.

Это приведет к развертыванию клиента на сервере и загрузке страницы index.html во внутренний браузер Eclipse. Лично я предпочитаю работать с веб-клиентом во внешнем браузере.

Веб-клиент доступен для любого браузера по адресу: http://localhost:8080/OAuth2.0_AuthCode.

В последующих разделах приводится детальный разбор кода клиента и показано, как проверить этот клиент с популярными OAuth 2.0-совместимыми серверами, такими как Salesforce, Google и IBM.

Код клиента OAuth 2.0

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

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

Входные параметры клиента передаются посредством файла параметров Oauth2Client.config, который находится в папке src проекта. Это следующие входные параметры:

  • scope: необязательный параметр. Определяет область запроса доступа. Ключ доступа, возвращенный сервером, обеспечивает доступ только к тем службам, которые входят в эту область.
  • state: необязательный параметр. Используется для сохранения состояния между клиентским запросом и ответом переадресации от сервера авторизации, чтобы обеспечить целостность клиента.
  • grant_type: должен иметь значение authorization_code, что соответствует гранту кода авторизации.
  • client_id: идентификатор клиента или потребителя, предоставляемый сервером ресурсов при регистрации приложения.
  • client_secret: секретный ключ клиента или потребителя, предоставляемый сервером ресурсов при регистрации приложения.
  • access_token: ключ доступа, возвращенный сервером авторизации в ответ на действительный и авторизованный запрос ключа доступа. Код авторизации, возвращенный сервером авторизации, обменивается на ключ доступа.
  • refresh_token: ключ обновления, возвращенный сервером авторизации в ответ на действительный и авторизованный запрос ключа доступа. Ключ обновления можно использовать для обновления ключа доступа с истекшим сроком действия без необходимости присутствия владельца ресурса для повторной проверки подлинности. Клиент должен явным образом запросить ключ обновления у сервера.
  • redirect_uri: URI, на который сервер авторизации будет перенаправлять агент пользователя при запросе кода авторизации. Код авторизации направляется на этот URI.
  • authentication_server_url: это сервер авторизации. Все запросы кода авторизации необходимо направлять в этот URL-адрес.
  • token_endpoint_url: конечная точка выдачи ключей. Все запросы на получение ключей доступа и ключей обновления необходимо направлять на этот URL-адрес.
  • resource_server_url: URL-адрес сервера ресурсов, по которому нужно обращаться, чтобы получить доступ к защищенному ресурсу, передавая ему ключ доступа в заголовке авторизации.
  • approval_prompt_key: имя параметра, который используется сервером авторизации для определения состояния формы утверждения. Как правило, каждый сервер авторизации (Salesforce, Google, IBM и т.д.) имеет специальный параметр, который необходимо передать в составе запроса кода авторизации, чтобы указать, должен ли клиент предлагать форму утверждения каждого запроса. Имя этого параметра в Google — approval_prompt. В Salesforce это login consent.
  • access_type_key: имя параметра, который используется сервером авторизации для определения типа доступа. Как правило, каждый сервер авторизации (Salesforce, Google, IBM и т.д.) имеет специальный метод, с помощью которого клиент может в запросе ключа доступа сообщить, что вместе с ключом доступа ему нужно получить ключ обновления. В Google это делается посредством параметра access_type. В SalesForce требуется ввести значение refresh_token в качестве области действия.
  • access_type_value: значение параметра access_type_key. В Google необходимо передать значение offline, чтобы сервер, помимо ключа доступа, выслал ключ обновления.

Страница index.html примера кода клиента показана на рисунке 2. Эта страница открывается после успешной настройки проекта в Eclipse и его развертывания в Tomcat.

Рисунок 2. Домашняя страница примера клиента
Домашняя страница примера клиента
Домашняя страница примера клиента

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

Чтобы запустить клиент:

  • сначала введите все необходимые значения в файл Oauth2Client.config;
  • нажмите кнопку Start Test, чтобы получить ключ доступа. HTTP-запрос GET направляется в сервлет OAuth2Client, доступный по следующему URI: http://localhost:8080/OAuth2.0_AuthCode/handler;
  • код интерфейса пользователя передает следующий параметр запроса в рамках вызова сервлета OAuth2Client: caller=token (access token request), caller=resource (access protected resource request).

Код клиента, приведенный в листинге 6, – это выдержка из метода doGet класса OAuth2Client.

Листинг 6. Фрагмент метода doGet из примера кода клиента
String caller = request.getParameter(OAuthConstants.CALLER);
String code = request.getParameter(OAuthConstants.CODE);

//Загрузка файла параметров
Properties config = OAuthUtils.getClientConfigProps(OAuthConstants.CONFIG_FILE_PATH);

//Создание компонента OAuthDetails из файла параметров config 
OAuth2Details oauthDetails = OAuthUtils.createOAuthDetails(config);

//Проверка входных данных
List<String> invalidProps = OAuthUtils.validateInput(oauthDetails);
	if(invalidProps!=null && invalidProps.size() == 0){
		//Проверка прошла успешно

		if(OAuthUtils.isValid(caller)){
			//Запрос отправлен из веб-приложения.
			//Проверка типа запроса
			if(caller.equalsIgnoreCase(OAuthConstants.TOKEN)){
				//Запрос ключа доступа
				oauthDetails.setAccessTokenRequest(true);
				String location = 			 		
				OAuthUtils.getAuthorizationCode(oauthDetails);

				//Переадресация в место, указанное конечной точкой
				response.sendRedirect(location);
				return;
		}
		else{
			//Запрос доступа к защищенному ресурсу
				if(!OAuthUtils.isValid(oauthDetails.getResourceServerUrl())){
					invalidProps.add(OAuthConstants.RESOURCE_SERVER_URL);

				}

				if(!OAuthUtils.isValid(oauthDetails.getAccessToken())){
					if(!OAuthUtils.isValid(oauthDetails.getRefreshToken())){
						invalidProps.add(OAuthConstants.REFRESH_TOKEN);
					}

				}
				if(invalidProps.size() > 0){
					sendError(response, invalidProps);
					return;
				}

				Map<String,String> map = OAuthUtils.getProtectedResource(oauthDetails);
				response.getWriter().println(new Gson().toJson(map));
				return;
			}
		}
		else if(OAuthUtils.isValid(code)){
			//Обратный вызов из конечной точки с кодом.
			Map<String,String> map = OAuthUtils.getAccessToken(oauthDetails, code);
			response.getWriter().println(new Gson().toJson(map));
			return;
		}
		else{
			//Неверный запрос/ошибочный ответ
			String queryString = request.getQueryString();
			String error = "Invalid request";
			if(OAuthUtils.isValid(queryString)){
				//Конечная точка возвращает сообщение об ошибке
				error = queryString;
			}
			response.getWriter().println(error);
			return;

			}
		}
		else{
			//Недопустимые входные данные. Ошибка отправления
			sendError(response, invalidProps);
			return;

		}

Замечания к листингу 6.

  • Клиент получает параметры запроса, caller и code. Если запрос был отправлен интерфейсом пользователя, как показано выше, то параметр caller будет иметь допустимое значение; в противном случае запрос отправлен сервером авторизации в рамках вызова переадресации, и code будет иметь допустимое значение.
  • Затем код клиента создает компонент OAuthDetails, считывая параметры из файла OAuth2Client.config.
  • После этого компонент проверяется на правильность и полноту. Если найдены недопустимые или отсутствующие параметры, то в пользовательский интерфейс направляется соответствующее сообщение об ошибке с указанием недостающих/недопустимых параметров.
  • Затем код клиента переходит к проверке и вызову запрошенной операции.

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

В коде, приведенном в листинге 7, показано, как сделать запрос кода авторизации.

Листинг 7. Код запроса кода авторизации
HttpPost post = new HttpPost(oauthDetails.getAuthenticationServerUrl());
String location = null;

String state = oauthDetails.getState();

List<BasicNameValuePair> parametersBody = new ArrayList<BasicNameValuePair>();

parametersBody.add(new BasicNameValuePair(OAuthConstants.RESPONSE_TYPE,
		OAuthConstants.CODE));

parametersBody.add(new BasicNameValuePair(OAuthConstants.CLIENT_ID,
		oauthDetails.getClientId()));

parametersBody.add(new BasicNameValuePair(OAuthConstants.REDIRECT_URI,
		oauthDetails.getRedirectURI()));

if (isValid(oauthDetails.getScope())) {
	parametersBody.add(new BasicNameValuePair(OAuthConstants.SCOPE,
			oauthDetails.getScope()));
}

if (isValid(oauthDetails.getApprovalPromptValue())) {
	parametersBody.add(new BasicNameValuePair(
			oauthDetails.getApprovalPromptKey(), oauthDetails
					.getApprovalPromptValue()));
}

if (isValid(oauthDetails.getAccessTypeValue())) {
	parametersBody.add(new BasicNameValuePair(
			oauthDetails.getAccessTypeKey(), oauthDetails
					.getAccessTypeValue()));
}

if (isValid(state)) {
	parametersBody.add(new BasicNameValuePair(OAuthConstants.STATE,
			oauthDetails.getState()));
}

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();
	System.out.println("Code " + code);
	Map<String, String> map = handleURLEncodedResponse(response);

	if (OAuthConstants.HTTP_SEND_REDIRECT == code) {
		location = getHeader(response.getAllHeaders(),
				OAuthConstants.LOCATION_HEADER);
		if (location == null) {
			System.out
					.println("The endpoint did not pass in valid location header for redirect");
			throw new RuntimeException(
					"The endpoint did not pass in valid location header for redirect");
		}
	} else {
		System.out
				.println("Was expecting code 302 from endpoint to indicate redirect. Recieved httpCode "
						+ code);
		throw new RuntimeException(
				"Was expecting code 302 from endpoint to indicate redirect. Recieved httpCode "
						+ code);
	}

} catch (ClientProtocolException e) {
	// Автоматически сгенерированный блок-ловушка TODO
	e.printStackTrace();
	throw new RuntimeException(e.getMessage());
} catch (IOException e) {
	// Автоматически сгенерированный блок-ловушка TODO
	e.printStackTrace();
	throw new RuntimeException(e.getMessage());
}

return location;

Замечания по коду из листинга 7.

  • Код начинается с создания метода HttpPost, который используется для передачи параметров в URL-кодировке.
  • response_type, client_id и redirect_uri – это обязательные параметры, которые включаются в запрос.
  • Другие, необязательные параметры, такие как state, scope, approval_prompt_key/value и access_type_key/value, добавляются, если в файле конфигурации содержались их действительные значения.
  • DefaultHttpClient используется для запроса к серверу авторизации.
  • Сервер авторизации проверяет параметры запроса и отвечает HTTP-кодом переадресации 302 и заголовком Location.
  • Программа распознает код 302, полученный от сервера авторизации, и извлекает из заголовков ответа Location.
  • Заголовок Location содержит URI, необходимый клиенту для переадресации агента пользователя (веб-браузер). URI, как правило, – это приглашение для владельца ресурса войти в систему и дать подтверждение клиента.
  • Местоположение URI возвращается в вызывающий метод (OAuth2Client.doGet()).
  • Метод Oauth2Client.doGet() перенаправляет ответ по адресу URI.
  • Теперь агент пользователя владельца ресурса (веб-браузер) перенаправляется на страницу входа/утверждения, где владелец ресурса должен войти и утвердить клиента, выполняющего запрос.
  • После этого сервер авторизации использует URI переадресации, переданный в исходном запросе кода авторизации, для отправки значения code.

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

Листинг 8. Пример кода запроса ключа доступа
HttpPost post = new HttpPost(oauthDetails.getTokenEndpointUrl());
		String clientId = oauthDetails.getClientId();
		String clientSecret = oauthDetails.getClientSecret();
		String scope = oauthDetails.getScope();
		Map<String, String> map = new HashMap<String, String>();

		List<BasicNameValuePair> parametersBody = new ArrayList<BasicNameValuePair>();

		parametersBody.add(new BasicNameValuePair(OAuthConstants.GRANT_TYPE,
				oauthDetails.getGrantType()));

		parametersBody.add(new BasicNameValuePair(OAuthConstants.CODE,
				authorizationCode));

		parametersBody.add(new BasicNameValuePair(OAuthConstants.CLIENT_ID,
				clientId));

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

		parametersBody.add(new BasicNameValuePair(OAuthConstants.REDIRECT_URI,
				oauthDetails.getRedirectURI()));

		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();

			map = handleResponse(response);
			accessToken = map.get(OAuthConstants.ACCESS_TOKEN);

		} catch (ClientProtocolException e) {
			// Автоматически сгенерированный блок-ловушка TODO
			e.printStackTrace();
			throw new RuntimeException(e.getMessage());
		} catch (IOException e) {
			// Автоматически сгенерированный блок-ловушка TODO
			e.printStackTrace();
			throw new RuntimeException(e.getMessage());
		}

		return map;

Замечания к листингу 8.

  • Для запроса ключа доступа к конечной точке выдачи ключей использует метод HttpPost.
  • grant_type, code и client_id – это обязательные параметры.
  • Если client_secret действителен, то он также включается в запрос.
  • Значение code – это код, возвращенный сервером авторизации в предыдущем запросе.
  • Если запрос действителен, то конечная точка выдачи ключей возвращает ключ доступа и маркер обновления, если это запрос доступа в автономном режиме.
  • Обратите внимание, что URI переадресации, передаваемый в этом запросе, должен быть идентичен тому, что отправлен в запросе кода авторизации. Конечная точка выдачи ключей проверяет, что URI переадресации совпадает с указанным в приложении клиента, данные которого доступны для конечной точки выдачи ключей.

Ключ обновления

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

Листинг 9. Пример кода обновления ключа доступа
String clientId = oauthDetails.getClientId();
String clientSecret = oauthDetails.getClientSecret();
String scope = oauthDetails.getScope();
String refreshToken = oauthDetails.getRefreshToken();
Map<String, String> map = new HashMap<String, String>();

		if (!isValid(refreshToken)) {
			throw new RuntimeException(
					"Please provide valid refresh token in config file");
		}

		List<BasicNameValuePair> parametersBody = new ArrayList<BasicNameValuePair>();

		parametersBody.add(new BasicNameValuePair(OAuthConstants.GRANT_TYPE,
				OAuthConstants.REFRESH_TOKEN));

		parametersBody.add(new BasicNameValuePair(OAuthConstants.REFRESH_TOKEN,
				oauthDetails.getRefreshToken()));

		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;
		try {
			post.setEntity(new UrlEncodedFormEntity(parametersBody, HTTP.UTF_8));

			response = client.execute(post);
			int code = response.getStatusLine().getStatusCode();

			map = handleResponse(response);

		} catch (ClientProtocolException e) {
			// Автоматически сгенерированный блок-ловушка TODO
			e.printStackTrace();
			throw new RuntimeException(e.getMessage());
		} catch (IOException e) {
			// Автоматически сгенерированный блок-ловушка TODO
			e.printStackTrace();
			throw new RuntimeException(e.getMessage());
		}

		return map;

Замечания к листингу 9.

  • Для отправки запроса к конечной точке выдачи ключей код использует метод HttpPost.
  • Параметры grant_type и refresh_token обязательны.
  • Если параметры client_id, client_secret и scope действительны, то они также включаются в запрос.
  • Если запрос действителен, то конечная точка выдачи ключей возвращает новый ключ доступа.

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

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

Листинг 10. Пример кода доступа к защищенному ресурсу
String resourceURL = oauthDetails.getResourceServerUrl();

		Map<String, String> map = new HashMap<String, String>();

		HttpGet get = new HttpGet(resourceURL);
		get.addHeader(OAuthConstants.AUTHORIZATION,
				getAuthorizationHeaderForAccessToken(oauthDetails
						.getAccessToken()));
		DefaultHttpClient client = new DefaultHttpClient();
		HttpResponse response = null;
		String accessToken = null;
		int code = -1;
		try {
			response = client.execute(get);
			code = response.getStatusLine().getStatusCode();
			if (code == OAuthConstants.HTTP_UNAUTHORIZED
					|| code == OAuthConstants.HTTP_FORBIDDEN) {
				// Access token is invalid or expired.Regenerate the access
				// token
				System.out
						.println("Access token is invalid or expired. Refreshing access token....");
				map = refreshAccessToken(oauthDetails);
				accessToken = map.get(OAuthConstants.ACCESS_TOKEN);

				if (isValid(accessToken)) {
					// update the access token
					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 refresh access token");
				}

			}

			map = handleResponse(response);

		} catch (ClientProtocolException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			get.releaseConnection();
		}

		return map;

Замечания к листингу 10.

  • Этот метод заполняет компонент OauthDetails значениями из файла конфигурации.
  • Как подсказывает название, этот метод использует простой метод HttpGet, чтобы получить защищенный ресурс на сервере ресурсов.
  • Для аутентификации на сервере ресурсов нужно передать ключ доступа в заголовке авторизации. Например, Authorization: Bearer accessTokenValue.
  • Код создает компонент DefaultHttpClient, чтобы сделать запрос get к серверу ресурсов.
  • Если код ответа сервера ресурсов 401 или 403, то ключ доступа, используемый для аутентификации, недействителен или просрочен.
  • На следующем шаге выполняется регенерация ключа доступа (см. Листинг 9).
  • После успешной регенерации ключа доступа код обновляет значение ключа доступа в компоненте OauthDetails. Он заменяет существующий заголовок авторизации в методе get новым значением ключа доступа.
  • Затем код делает другой запрос доступа к защищенному ресурсу.
  • Если ключ доступа действителен и URL-адрес сервера ресурсов правильный, то вы увидите содержимое ответа в окне консоли.

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

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

Salesforce.com – это популярное приложение типа «программное обеспечение как услуга» (SaaS), которое поддерживает грант кода авторизации OAuth 2.0.

Для регистрации в Salseforce.com:

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

Выполнение примера клиента в Salesforce.com

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

Файл Oauth2Client_salesforce.config находится в папке Java resources/src. Это файл конфигурации, настроенный для salesforce.com, который можно использовать в качестве шаблона для передачи значений конфигурации при тестировании в Salesforce.com.

Запустите веб-приложение Oauth2Client на сервере Tomcat, как показано выше, чтобы открыть начальную страницу проверочного клиента (см. Рисунок 3).

Рисунок 3. Страница проверочного клиента SalesForce
Страница проверочного клиента SalesForce
Страница проверочного клиента SalesForce

Рисунок 3 иллюстрирует страницу проверочного клиента. Нажмите кнопку Start Test, чтобы получить ключ доступа. Вы увидите приглашение войти для запроса ключа доступа Salesforce, как показано на рисунке 4.

Рисунок 4. Запрос ключа доступа Salesforce (приглашение войти)
Приглашение войти в систему
Приглашение войти в систему

На рисунке 4 показана страница входа Salesforce, на которую клиент перенаправляет агент пользователя (веб-браузер) после получения от Salesforce.com сообщения 302 и заголовка Location.

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

Рисунок 5. Запрос ключа доступа Salesforce (приглашение подтвердить)
Экран запроса доступа
Экран запроса доступа

На рисунке 5 показано предложение владельцу ресурса (после входа в систему) подтвердить предоставление доступа к приложению VarunOAuth2.0, которое представляет собой пример кода, запрашивающий доступ к данным владельца ресурса.

Ответ SalesForce с ключом доступа

После того как вы утвердите предоставление клиенту доступа по предложению Salesforce, конечная точка выдачи ключей вышлет ключи доступа и обновления вместе с другими данными, относящимися к Salesforce. Вы должны увидеть в окне консоли Eclipse результат, показанный в листинге 11.

Листинг 11. Ответ SalesForce с ключом доступа
********** Response Received **********
  id = https://login.salesforce.com/id/00D90000000mQaYEAU/00590000001HCB7AAO
  issued_at = 1408356835704
  scope = full refresh_token
  instance_url = https://ap1.salesforce.com
  token_type = Bearer
  refresh_token = 5Aep8617VFpoP.M.4vPT_5eQXhIJTvFPNyK2GaBz7xFooRQE590MJSZNVqfTXKUqoiZH_yhm_ZpaVsmp
  id_token = eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjE5MCJ9.eyJleHAiOjE0MDgzNTY5NTUsInN1YiI6Imh0dHBzOi8vbG9naW4uc2FsZXNmb3JjZS5jb20vaWQvMDBEOTAwMDAwMDBtUWFZRUFVLzAwNTkwMDAwMDAxSENCN0FBTyIsImF0X2hhc2giOiJ3eWJpZ1NRTEtaNjdiYlRxWUJxNEVnIiwiYXVkIjoiM01WRzlZNmRfQnRwNHhwNXhLcHgxcFBtcFZidnlUV09Gd2FWbkxwOFpDYjRZVjBRX2FJcHI2WTZVazd1ZHcxcWtCTFZQcVVFNWY2blU1NW5xTWljMCIsImlzcyI6Imh0dHBzOi8vbG9naW4uc2FsZXNmb3JjZS5jb20iLCJpYXQiOjE0MDgzNTY4MzV9.W-r2-k-creIijmRmZ5qQff-bonjjtynNivJAv5XaSGedGhPavWlQpmn76B9k5vB7TWUDTX6y2NroTuIpBi-F2jrO0dunN1giPfv-YKyrCYrpy75J7NXmnSnDGrKhhYgkcR7x9s9MLMoMD-Vf1wsJr58XU2He-UwfrihfkdJzvLKiWRNQfz1gCUwlNSws70AQSqrBfB6MHpLqE7ogG1M3SOp6B4hbcA8NC_zwnvJwIDmF4t3_rf6VsgPuPjV_t4PphBhbrln7sm4b9OMcRRycc8WcCvgBvNPjI37uImciDYUGIP25NEy5sRM3mEU392YlmR5AoHUqOVOqdO9DS1ULWxBy3Q-Fp1wyKYyWiCrUMXe5QdtmeBmkpzptCKXwAhfxhLBdai4bBFfh8K3If4UP-WeNcpMwNkiRhVElwntlqtCPaSs4BLiZGonpLLgwET-f_Iyxs4BauCNvyWDbme_2it2V5AEHgJoKvuf2oU6hD8-Sit8MsdEdc2ugf-nk96QJt5px3QvChfDIE8B7W5trzXagkvzVcXXJV06zJbDUf-ioz7zDTI4Popkxlb31cQiaLAtz2IxIUtjZAfAcTXJaxi8txjV8glqS8Z61145bUaitXgGmZhZAqeefrqLneyCDD--EPNWDIdQYSPhRPbtFb5Aa89AMDNIePxTNnIWShNs
  access_token = 00D90000000mQaY!AQ8AQMgSI4eZOjvKJGVPuWISCkuS1WC0R6gOWpMg57bMwVWGkyS9_Wraa5ooMrjTfaqMmXmXlgParPk1rjn0vH5BxbNRq3W0
  signature = o1VBoC/PzvMw08hZGxYvXLiV/SXQirHBl8qOrk1Mu6Q=

Получение пользователем информация от сервера Salesforce

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

Добавьте ключ доступа и ключ обновления в файл Oauth2Client.confg. Кроме того, установите параметр URL сервера ресурсов, указав URL, который требуется протестировать. Например, можно настроить его в соответствии с полем id, которое Salesforce возвратил в своем ответе на запрос ключа доступа. Поле id используется для доступа к данным учетной записи владельца ресурса (например, https://login.salesforce.com/id/00D90000000mQaYEAU/00590000001HCB7AAO).

Обновите проект и снова запустите его на сервере Tomcat. Нажмите кнопку Start Test, чтобы обратиться к защищенным ресурсам.

В окне консоли Eclipse появится текст, подобный листингу 12.

Листинг 12. Распечатка информации с защищенного ресурса SalesForce
********** Response Received **********
  addr_city = null
  email_verified = true
  locale = en_US
  addr_state = Unknown
  asserted_user = true
  nick_name = vern.ojha1********
  id = https://login.salesforce.com/id/00D9000000QaYEAU/005900001HCB7AAO
  first_name = varun
  timezone = America/Los_Angeles
  username = *******
  mobile_phone = null
  user_id = ************
  addr_street = null
  status = {"created_date":null,"body":null}
  user_type = STANDARD
  urls = {"sobjects":"https:\/\/ap1.salesforce.com\/services\/data\/v{version}\/sobjects\/","feeds":"https:\/\/ap1.salesforce.com\/services\/data\/v{version}\/chatter\/feeds","users":"https:\/\/ap1.salesforce.com\/services\/data\/v{version}\/chatter\/users","query":"https:\/\/ap1.salesforce.com\/services\/data\/v{version}\/query\/","enterprise":"https:\/\/ap1.salesforce.com\/services\/Soap\/c\/{version}\/00D90000000mQaY","recent":"https:\/\/ap1.salesforce.com\/services\/data\/v{version}\/recent\/","feed_items":"https:\/\/ap1.salesforce.com\/services\/data\/v{version}\/chatter\/feed-items","search":"https:\/\/ap1.salesforce.com\/services\/data\/v{version}\/search\/","partner":"https:\/\/ap1.salesforce.com\/services\/Soap\/u\/{version}\/00D90000000mQaY","rest":"https:\/\/ap1.salesforce.com\/services\/data\/v{version}\/","groups":"https:\/\/ap1.salesforce.com\/services\/data\/v{version}\/chatter\/groups","metadata":"https:\/\/ap1.salesforce.com\/services\/Soap\/m\/{version}\/00D90000000mQaY","profile":"https:\/\/ap1.salesforce.com\/0000001CB7AAO"}
  mobile_phone_verified = false
  is_app_installed = true
  photos = {"picture":"https:\/\/c.ap1.content.force.com\/profilephoto\/005\/F","thumbnail":"https:\/\/c.ap1.content.force.com\/profilephoto\/005\/T"}
  display_name = varun ojha
  last_modified_date = 2013-06-04T07:43:42.000+0000
  email = **************
  addr_country = IN
  organization_id = 00D90000000mQaYEAU
  last_name = ojha
  utcOffset = -28800000
  active = true
  language = en_US
  addr_zip = 560071

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

Регистрация в Google

Google использует OAuth 2.0 для аутентификации API, которые могут использоваться для доступа к таким службам, как GoogleDrive, TaskQueue и CloudSQL. Чтобы настроить приложение для тестирования клиента OAuth 2.0 с Google, следуйте инструкциям, приведенным на странице https://developers.google.com/accounts/docs/OAuth2?hl=ES.

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

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

Файл Oauth2Client_Google.config находится в Java resources/src. Этот файл конфигурации настраивается для работы со службами Google и может использоваться в качестве шаблона для предоставления значений конфигурации.

Запустите веб-приложение Oauth2Client на сервере Tomcat, как показано выше. Процесс и результаты операций по получению ключа доступа и выполнению доступа к защищенным ресурсам показаны на рисунке 6.

Рисунок 6. Запрос ключа доступа Salesforce (приглашение войти)
Запрос ключа доступа Salesforce (приглашение войти)
Запрос ключа доступа Salesforce (приглашение войти)

На рисунке 6 показана страница входа Google, на которую клиент перенаправляет агент пользователя (веб-браузер) после получения от Google сообщения 302 и заголовка Location.

На рисунке 7 показано предложение владельцу ресурса (после входа в систему) подтвердить предоставление доступа к приложению Cast Iron App, которое обращается к данным владельца ресурса.

Рисунок 7. Запрос ключа доступа (приглашение подтвердить)
Экран подтверждения Cast Iron
Экран подтверждения Cast Iron

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

Листинг 13.
********** Response Received **********
  expires_in = 3600
  token_type = Bearer
  refresh_token = 1/TtCxaFlKMRsHeIlxrY-2ZJIO8DcRmQEiQ_2Wxw8
  access_token = ya29.ZQDpI-ahF6TMURwAAABqBu-2-U0_lUWfbwh053j3db3PzaNXV4k_k6fc_VT7uQ

Получение сведений о пользователе от сервера Google

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

Отредактируйте файл Oauth2Client.confg, указав ключ доступа и ключ обновления, и заполните параметр url, указав URL-адрес сервера ресурсов, который вы хотите протестировать. Я заменю его на https://www.googleapis.com/drive/v2/about, который представляет собой URL-адрес учетной записи GoogleDrive.

Теперь обновите проект и снова запустите его на сервере Tomcat. Нажмите кнопку Start Test, чтобы обратиться к защищенному ресурсу.

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

Листинг 14.
********** Response Received **********
  name = Varun Ojha
  features = [{"featureName":"ocr"},{"featureName":"translation","featureRate":0.5}]
  quotaBytesTotal = 16106127360
  largestChangeId = 1911
  rootFolderId = 0ALupA2Opqyp1Uk9PVA
  quotaType = LIMITED
  quotaBytesByService = [{"bytesUsed":"229","serviceName":"DRIVE"},{"bytesUsed":"1154860956","serviceName":"GMAIL"},{"bytesUsed":"568174452","serviceName":"PHOTOS"}]
  permissionId = 01822620516456565194
  quotaBytesUsedInTrash = 119332

Как видите, вы успешно получили доступ к информации из учетной записи владельца ресурса GoogleDrive с использованием метода аутентификации OAuth 2.0.

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

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

Этот пример кода клиента успешно протестирован с OAuth 2.0-совместимыми конечными точками IBM, а именно, IBM® WebSphere® Application Server и IBM Datapower®.

Инструкции по настройке сервера OAuth 2.0 на сервере Websphere Application Server находятся в документе Использование OAuth: Подключение поставщика услуг OAuth к WebSphere Application Server.

Конечные точки IBM можно тестировать тем же способом, что и Salesforce или Google.

Заключение

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


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


Похожие темы

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