我正在开发使用Python编写并使用Endpoints API的Google App Engine应用程序。与此同时,我正在编写Chrome扩展程序以与Endpoints API进行交互。我一直在使用Endpoints API和授权遇到很多问题。目前,这是我的设置:
from google.appengine.ext import endpoints
from protorpc import message_types
from protorpc import remote
ALLOWED_CLIENT_IDS = ['client_id_from_google_api_console',
endpoints.API_EXPLORER_CLIENT_ID]
@endpoints.api(name='my_api',version='v1', description='My API',
allowed_client_ids=ALLOWED_CLIENT_IDS)
class MyApi(remote.Service):
@endpoints.method(message_types.VoidMessage, DeviceListResponse,
name='user.device.list', path='user/device/list',
http_method='GET')
def user_device_list(self, request):
current_user = endpoints.get_current_user()
if current_user is None:
raise endpoints.UnauthorizedException('You must authenticate first.')
if current_user.user_id() is None:
raise endpoints.NotFoundException("Your user id was not found.")
return DeviceListResponse(devices=[]) #Hypothetically return devices
api_service = endpoints.api_server([MyApi], restricted=False)
JS的起源包括: 铬的扩展:// chrome_app_id
var apiRoot = "https://my_app_id.appspot.com/_ah/api";
var clientID = "client_id_from_google_api_console";
var oauthScopes = ["https://www.googleapis.com/auth/userinfo.email"];
var responseType = "token id_token";
//Helper method to log to the console
function l(o){console.log(o);}
function oauthSignin(mode) {
gapi.auth.authorize({client_id: clientID, scope: oauthScopes,
immediate: mode, response_type: responseType}, function() {
var request = gapi.client.oauth2.userinfo.get();
request.execute(function(resp) {
authenticated = !resp.code;
if(authenticated) {
var token = gapi.auth.getToken();
token.access_token = token.id_token;
gapi.auth.setToken(token);
l("Successfully authenticated. Loading device list");
gapi.client.my_api.user.device.list({}).execute(function(resp) {
if(resp.code) {
l("Response from device list: " + resp.message);
}
l(resp);
});
}
});
});
}
//This get's called when the page and js library has loaded.
function jsClientLoad() {
l("JS Client Libary loaded. Now loading my_api and oauth2 APIs.");
var apisToLoad;
var callback = function() {
if (--apisToLoad == 0) {
l("APIs have loaded.")
oauthSignin(true);
} else {
l("Waiting for " + apisToLoad + " API" + (apisToLoad>1?"s":"") + " to load.");
}
}
apisToLoad = 2; // must match number of calls to gapi.client.load()
gapi.client.load('my_api', 'v1', callback, apiRoot);
gapi.client.load('oauth2', 'v2', callback);
}
现在我已经展示了我的代码的主要部分(请注意,我必须在不上传整个代码的情况下将其更改为有意义),如果我转到Google API Explorer并运行该方法,我会获得200响应。如果我在Chrome扩展程序中运行它,我会收到一条404代码,其中包含“找不到您的用户ID”。
答案 0 :(得分:19)
目前还不清楚为什么/何时会导致200
;它不应该。如Function User.getUserId() in Cloud endpoint api returns null for a user object that is not null中所述,这是known issue。
user_id
将从endpoints.get_current_user()
返回的结果中填充从不。存在一种解决方法:通过将用户对象存储在数据存储区中然后检索它(使用新的get,如果您使用的是ndb
),将填充user_id()
值。
您应该强烈考虑使用与该帐户关联的Google个人资料ID,而不是App Engine用户ID。
endpoints
旨在与承载令牌和ID令牌(适用于Android)一起使用。 ID令牌是与设备加密一起签名的特殊类型的JWT(JSON Web令牌)。因此,从这些令牌中解析用户只能确定该令牌中编码的信息(有关详细信息,请参阅Cloud endpoints oauth2 error。)
由于这些令牌由App Engine之外的通用Google身份验证提供程序(OAuth 2.0)创建,因此该服务不知道/共享App Engine用户ID。因此,当使用ID令牌对请求进行签名时,从不可以填充user_id()
。
使用标准的持票人令牌(适用于您的Chrome应用程序)时,会使用App Engine OAuth API。 OAuth API调用
时oauth.get_current_user(some_scope)
(oauth
为google.appengine.api.oauth
),
oauth.oauth_api._maybe_call_get_oauth_user(_scope=None)
method被调用。这使得RPC成为共享的App Engine层,该层提供能够从令牌获取当前用户的服务。在这种情况中,返回的用户的<{1}} 将设置,但是,user_id()
的用户值不会保留,只有电子邮件和auth域名。
endpoints.get_current_user
调用只有昂贵的 IF 才能生成RPC。 oauth.get_current_user()
方法存储上次调用的值,因此第二次调用_maybe_call_get_oauth_user
将不会产生网络/速度开销,除了从Python oauth.get_current_user()
查找值的几纳秒之外。
这一点至关重要,因为dict
使用endpoints.get_current_user()
调用来确定持有者令牌用户,所以如果你想再次调用它,你就会担心这种性能。
如果您知道自己永远不会使用ID令牌或者可以轻松确定这些情况,那么您可以将代码更改为只调用两者:
oauth.get_current_user()
注意:我们仍然必须调用endpoints_user = endpoints.get_current_user()
if endpoints_user is None:
raise endpoints.UnauthorizedException(...)
oauth_user = oauth.get_current_user(known_scope)
if oauth_user is None or oauth_user.user_id() is None:
# This should never happen
raise endpoints.NotFoundException(...)
,因为它始终确保我们的令牌仅针对我们允许的特定范围之一和特定客户端之一我们已将白名单与我们的应用程序进行对话。
注意:值endpoints.get_current_user()
将根据您的可能范围与令牌匹配而有所不同。您的范围列表将在known_scope
helper methods之一中循环播放,如果成功,则最终匹配范围将存储为endpoints.get_current_user()
。我强烈建议将此值用于os.getenv('OAUTH_LAST_SCOPE')
。
如上所述,App Engine用户ID根本不能隐含在ID令牌中(当前),但是,可以使用Google Profile ID代替App Engine用户ID。 (此ID通常被视为Google+ ID,但这在许多服务中都是一致的。)
要确保此值与您的Bearer OR ID令牌相关联,请确保您还请求与userinfo
API相关联的非known_scope
范围之一:< / p>
userinfo.email
https://www.googleapis.com/auth/plus.login
https://www.googleapis.com/auth/plus.me
https://www.googleapis.com/auth/userinfo.email
(截至2013年5月20日撰写的此范围列表。)
与Bearer令牌案例中的App Engine用户ID类似,此{J} ID将被https://www.googleapis.com/auth/userinfo.profile
丢弃,但可用于两种令牌。
作为method示例一部分的endpoints.get_current_user()
appengine-picturesque-python
修补了get_google_plus_user_id()
辅助方法之一以保留此数据,并允许您使用此值而无需使用此值重复用于验证请求中的承载或ID令牌的昂贵网络调用。
答案 1 :(得分:2)
以防万一从1.8.6开始有人在这里,并且仍在尝试使用auth_util.py
解决方法来返回Google个人资料ID。 endpoints.token_id
现在有两种方法,具体取决于用户是否在开发服务器上。
在Google的服务器上,流程返回oauth_user
并且没有点击tokeninfo端点。因此,auth_util.py
中不会保存任何令牌信息。但是,在开发服务器上,它确实命中了tokeninfo端点,因此按预期工作。
对我而言,解决这个问题的最简单方法就是将补丁endpoints.token_id._is_local_dev
设置为并将其设置为始终为真。
答案 2 :(得分:0)
我刚刚遇到这个头疼的问题。但是,经过一些测试后,似乎在本地开发服务器上使用1.9.2 user.user_id()
返回None
。但是,如果部署,它将返回一个值。不确定这是App Engine ID还是Google+ ID。无论如何要找出来?
但是,鉴于文档,我认为它确实返回的ID可以安全地用于持久性记录。