13

我正在开发用 Python 编写并使用 Endpoints API 的 Google App Engine 应用程序。同时,我正在编写一个 Chrome 扩展程序来与 Endpoints API 交互。我在 Endpoints API 和授权方面遇到了很多问题。目前,这是我的设置:

端点 API (Python)

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)

谷歌 API 控制台

JS 来源包括:chrome-extensions://chrome_app_id

铬扩展 (JS)

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。”。

4

3 回答 3

19

目前尚不清楚为什么/何时会导致200; 它不应该。正如Cloud endpoint api 中的 Function User.getUserId() 中所述,对于非 null 的用户对象返回 null,这是一个已知问题

TLDR;

user_id永远不会填充到从 返回的结果中endpoints.get_current_user()。存在一种解决方法:通过将用户对象存储在数据存储中,然后检索它(使用新的 get,如果您正在使用ndb),user_id()将填充该值。

您应该强烈考虑使用与帐户关联的 Google 个人资料 ID,而不是 App Engine 用户 ID。

历史/解释:

endpoints旨在与 Bearer 令牌和 ID 令牌(适用于 Android)一起使用。ID 令牌是一种特殊类型的 JWT(JSON Web 令牌),与设备加密一起签名。因此,从这些令牌中解析用户只能确定该令牌中编码的信息(有关更多信息,请参阅云端点 oauth2 错误)。

由于这些令牌是由 App Engine 之外的通用 Google 身份验证提供程序 (OAuth 2.0) 生成的,因此该服务不知道/共享 App Engine 用户 ID。因此,在使用 ID 令牌对请求进行签名时,永远不可能填充。user_id()

当使用标准 Bearer 令牌(这对您的 Chrome 应用程序来说很好)时,将使用 App Engine OAuth API。当 OAuth API 调用时

oauth.get_current_user(some_scope)

(在哪里oauthgoogle.appengine.api.oauth

oauth.oauth_api._maybe_call_get_oauth_user(_scope=None)

方法被调用。这使得RPC到共享 App Engine 层,该层提供能够从令牌中获取当前用户的服务。在这种情况下user_id()设置返回用户的,但是,不会保留用户值,仅保留电子邮件和身份验证域。endpoints.get_current_user

另一种解决方法:

oauth.get_current_user()调用仅在进行 RPC 时才昂贵。该_maybe_call_get_oauth_user方法存储上次调用的值,因此oauth.get_current_user()第二次调用不会产生网络/速度开销,除了从 Python 查找值的几纳秒dict

这是至关重要的,因为endpoints.get_current_user()使用调用来oauth.get_current_user()确定 Bearer 令牌用户,所以如果你想再次调用它,你会担心性能。

如果您知道您永远不会使用 ID 令牌或可以轻松确定这些情况,您可以更改您的代码以调用两者:

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(),因为它始终确保我们的令牌仅针对我们允许的特定范围之一以及我们已列入白名单以与我们的应用程序对话的特定客户端 ID 之一铸造。

注意:该值known_scope将根据您的哪些可能范围与令牌匹配而有所不同。您的范围列表将在其中一个endpoints.get_current_user() 辅助方法中循环,如果成功,最终匹配范围将存储为os.getenv('OAUTH_LAST_SCOPE'). 我强烈建议将此值用于known_scope.

更好的选择:

如前所述,App Engine 用户 ID 根本无法从 ID 令牌中隐含(目前),但是,可以使用Google 个人资料 ID代替 App Engine 用户 ID。(此 ID 通常被视为 Google+ ID,尽管这在许多服务中是一致的。)

要确保此值与您的 Bearer OR ID 令牌相关联,请确保您还请求与APIuserinfo.email关联的非范围之一:userinfo

  • https://www.googleapis.com/auth/plus.login
  • https://www.googleapis.com/auth/plus.me
  • https://www.googleapis.com/auth/userinfo.email
  • https://www.googleapis.com/auth/userinfo.profile

(截至 2013 年 5 月 20 日撰写本文时,此范围列表为最新。)

与 Bearer 令牌案例中的 App Engine 用户 ID 类似,此 Google 个人资料 ID 被 丢弃endpoints.get_current_user()它可用于两种令牌。

作为示例的一部分的方法get_google_plus_user_id() 修补appengine-picturesque-pythonendpoints.get_current_user()帮助方法之一以保留此数据,并允许您使用此值,而无需重复用于验证请求中的承载或 ID 令牌的昂贵网络调用。

于 2013-05-21T03:43:39.423 回答
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并将其设置为始终为真。

于 2013-10-25T23:40:23.793 回答
0

我最近才遇到这种头痛。但是,经过一些测试后,似乎在本地开发服务器上user.user_id()返回了 1.9.2。None但是,如果您部署,它将返回一个值。不确定这是 App Engine ID 还是 Google+ ID。无论如何要找出来?

但是,鉴于文档,我假设它返回的 ID 可以安全地用于持久记录。

于 2014-04-13T15:48:13.603 回答