Не удается авторизовать подключение к собственному API конечных точек GAE с учетной записью службы
Я бьюсь головой об стену, пытаясь успешно разрешить попадание API в проект Google App Engine (GAE), который я запускаю из сценария python с использованием OAuth2 и служебной учетной записи.
Я создал учетную запись службы, добавил идентификатор учетной записи службы в идентификаторы разрешенных клиентов в файле API, преобразовал закрытый ключ из.p12 в.pem и авторизовал вызов httplib2. Я попытался передать учетные данные с помощью метода.authorize() и загрузить учетные данные как JSON и вручную добавить параметр access_token в заголовки - {"Authorization": "Bearer " + token_string}.
При каждом вызове API выдает "Invalid Token".
Одна странная вещь, которую я заметил в том, что когда я впервые вызываю 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 = "service_account_string@developer.gserviceaccount.com"
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."
}
}
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! Если у кого-то есть вопросы по этому поводу или мое решение неясно, пожалуйста, не стесняйтесь комментировать. Это был болезненный процесс, который я не желаю никому другому.