Не удается авторизовать подключение к API собственных конечных точек GAE со служебным аккаунтом.

Я бился головой об стену, пытаясь успешно авторизовать попадание API в проект Google App Engine (GAE), который я запускаю из скрипта python с использованием OAuth2 и учетной записи службы.

Я создал учетную запись службы, добавил идентификатор учетной записи службы к разрешенным идентификаторам клиентов в файле api, преобразовал закрытый ключ из .p12 в .pem и разрешил вызов httplib2. Я пробовал передать учетные данные с помощью метода .authorize (), загрузив учетные данные как JSON и вручную добавив параметр access_token в заголовки - {"Authorization": "Bearer " + token_string}.

Каждый вызов API также возвращает «Недействительный токен».

Я заметил одну странную вещь: когда я впервые вызываю SignedJwtAssertionCredentials, в учетных данных нет токена доступа - «access_token» равно None. Однако токен доступа появляется, когда я извлекаю учетные данные из файла .dat в хранилище.

Ниже приведены файлы GAE endpoints_api.py, python_test.py и ответ 401.

Любые идеи будут очень признательны.

Сначала файл сервера конечных точек движка приложения:

# endpoints_api.py running on GAE

import endpoints
import time
from protorpc import messages
from protorpc import message_types
from protorpc import remote

SERVICE_ACCOUNT_ID = 'random_account_id_string.apps.googleusercontent.com'
WEB_CLIENT_ID = 'random_web_client_id_string.apps.googleusercontent.com'
ANDROID_AUDIENCE = WEB_CLIENT_ID

package = "MyPackage"

class Status(messages.Message):
    message = messages.StringField(1)
    when = messages.IntegerField(2)

class StatusCollection(messages.Message):
    items = messages.MessageField(Status, 1, repeated=True)

STORED_STATUSES = StatusCollection(items=[
    Status(message='Go.', when=int(time.time())),
    Status(message='Balls.', when=int(time.time())),
    Status(message='Deep!', when=int(time.time())),
])

@endpoints.api(name='myserver', version='v1')
class MyServerApi(remote.Service):
    """MyServer API v1."""

    @endpoints.method(message_types.VoidMessage, StatusCollection,
                  allowed_client_ids=[SERVICE_ACCOUNT_ID,
                                      endpoints.API_EXPLORER_CLIENT_ID],
                  audiences=[ANDROID_AUDIENCE],
                  scopes=[endpoints.EMAIL_SCOPE],
                  path='status', http_method='GET',
                  name='statuses.listStatus')
    def statuses_list(self, unused_request):
        current_user = endpoints.get_current_user()
        if current_user is None:
            raise endpoints.UnauthorizedException('Invalid token.')
        else:
            return current_user.email(), STORED_STATUSES

APPLICATION = endpoints.api_server([MyServerApi])

следующий локальный скрипт python:

# python_test.py file running from local server

from apiclient.discovery import build
from oauth2client.file import Storage
from oauth2client.client import SignedJwtAssertionCredentials
import httplib2
import os.path
import json

SERVICE_ACCOUNT_EMAIL = "[email protected]"
ENDPOINT_URL = "http://my-project.appspot.com/_ah/api/myserver/v1/status"
SCOPE = 'https://www.googleapis.com/auth/userinfo.email'
SITE_ROOT = os.path.dirname(os.path.realpath(__file__))

f = file('%s/%s' % (SITE_ROOT, 'pk2.pem'), 'rb')
key = f.read()
f.close()

http = httplib2.Http()
storage = Storage('credentials.dat')
credentials = storage.get()

if credentials is None or credentials.invalid:
    credentials = SignedJwtAssertionCredentials(
        SERVICE_EMAIL, key, scope=SCOPE)
    storage.put(credentials)
else:
    credentials.refresh(http)

http = credentials.authorize(http)
headers = {'Content-Type': 'application/json'}

(resp, content) = http.request(ENDPOINT_URL,
                           "GET",
                           headers=headers)

print(resp)
print(content)

Наконец, консольный вывод:

{'status': '401', 'alternate-protocol': '443:quic,p=0.002', 'content-length': '238', 'x- xss-protection': '1; mode=block', 'x-content-type-options': 'nosniff', 'transfer-encoding': 'chunked', 'expires': 'Sun, 14 Sep 2014 23:51:36 GMT', 'server': 'GSE', '-content-encoding': 'gzip', 'cache-control': 'private, max-age=0', 'date': 'Sun, 14 Sep 2014 23:51:36 GMT', 'x-frame-options': 'SAMEORIGIN', 'content-type': 'application/json; charset=UTF-8', 'www-authenticate': 'Bearer realm="https://accounts.google.com/AuthSubRequest"'}

{
 "error": {
  "errors": [
   {
    "domain": "global",
    "reason": "required",
    "message": "Invalid token.",
    "locationType": "header",
    "location": "Authorization"
   }
  ],
  "code": 401,
  "message": "Invalid token."
 }
}

person TedCap    schedule 15.09.2014    source источник
comment
Вы должны использовать метод, описанный выше? Есть декоратор OAuth, который сделает всю работу за вас. Переписать код не составит большого труда.   -  person Ryan    schedule 15.09.2014
comment
developers.google.com/api-client-library/python/guide/ Вы даете ему свои данные OAtuh, и он обрабатывает токены и фактическую работу.   -  person Ryan    schedule 16.09.2014
comment
Я совершенно не понимаю, о чем говорю, но похоже, что это работает, только когда вы находитесь в среде движка приложения. Вам необходимо предоставить закрытый ключ, если вы находитесь на улице. У меня есть решение, которое делает что-то очень похожее, используя SignedJwtAssertionCredentials и build. Я отправлю это как ответ.   -  person TedCap    schedule 16.09.2014


Ответы (1)


Спасибо вам за помощь. Выше есть пара вещей, которые я сделал неправильно.

Во-первых, мне нужно было указать идентификатор проекта движка приложения в списке разрешенных идентификаторов.

API_ID = 'project-id-number.apps.googleusercontent.com'

@endpoints.method(message_types.VoidMessage, StatusCollection,
              allowed_client_ids=[API_ID, SERVICE_ACCOUNT_ID,
                                  endpoints.API_EXPLORER_CLIENT_ID],
              audiences=[ANDROID_AUDIENCE],
              scopes=[endpoints.EMAIL_SCOPE],
              path='status', http_method='GET',
              name='statuses.listStatus')

Я ошибочно пришел к выводу, что метод build () будет работать только с официальным Google APIS, но оказалось, что я неправильно ссылался на свой проект. Имея идентификатор проекта, я мог бы использовать build () вместо того, чтобы писать собственный вызов httplib2 в файл на стороне сервера (что не сработало).

Удалив код под http = credentials.authorize (http), я заменил его следующим:

myservice = build('my-service', 'v1', http=http, discoveryServiceUrl=discoveryServiceUrl)
data = myservice.my-path().endpoint-name()
results = data.execute()

Это успешно авторизует мою учетную запись и вызывает конечную точку. Woot! Если у кого-то еще есть вопросы по этому поводу, или если мое решение неясно, не стесняйтесь комментировать. Это был болезненный процесс, которого я больше не желаю.

person TedCap    schedule 15.09.2014