对于endpoints.get_current_user().user_id(),Google Endpoints API + Chrome Extension返回None

ben*_*dle 13 google-app-engine google-chrome-extension oauth-2.0 python-2.7 google-cloud-endpoints

我正在开发使用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)
Run Code Online (Sandbox Code Playgroud)

Google API控制台

JS的起源包括:chrome-extensions:// chrome_app_id

CHROME EXTENSION(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);
}
Run Code Online (Sandbox Code Playgroud)

结果

现在我已经展示了我的代码的主要部分(注意,我必须在不上传整个代码的情况下将其更改为有意义),如果我转到Google API Explorer并运行该方法,我会得到200响应.如果我在Chrome扩展程序中运行它,我会收到一条404代码,其中包含"找不到您的用户ID"的消息.

bos*_*ter 19

目前还不清楚为什么/何时会导致这种情况发生200; 它不应该.如云端点函数User.getUserId()中所述,api为非空的用户对象返回null,这是一个已知问题.

TLDR;

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

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

历史/说明:

endpoints用于承载令牌和ID令牌(适用于Android).ID令牌是与设备加密一起签名的特殊类型的JWT(JSON Web令牌).因此,从这些令牌中解析用户只能确定该令牌中编码的信息(有关详细信息,请参阅Cloud端点oauth2错误).

由于这些令牌由App Engine之外的通用Google Auth提供程序(OAuth 2.0)创建,因此该服务不知道/共享App Engine用户ID.因此,永远不可能填充user_id()ID令牌用于签署请求的时间.

使用标准的Bearer令牌(适用于您的Chrome应用程序)时,会使用App Engine OAuth API.OAuth API调用时

oauth.get_current_user(some_scope)
Run Code Online (Sandbox Code Playgroud)

(其中,oauthgoogle.appengine.api.oauth),则

oauth.oauth_api._maybe_call_get_oauth_user(_scope=None)
Run Code Online (Sandbox Code Playgroud)

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

另一种解决方法:

oauth.get_current_user()电话只有昂贵的IF它使RPC.该_maybe_call_get_oauth_user方法存储来自最后一次调用的值,因此oauth.get_current_user()第二次调用将不会产生网络/速度开销,而不是几纳秒来从Python查找值dict.

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

如果您知道自己永远不会使用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(...)
Run Code Online (Sandbox Code Playgroud)

注意:我们仍然必须打电话,endpoints.get_current_user()因为它始终确保我们的令牌仅针对我们允许的特定范围之一以及我们已列入白名单与我们的应用程序通信的特定客户端ID之一.

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

更好的选择:

如上所述,App Engine用户ID根据ID令牌(当前)无法隐含,但是,可以使用Google Profile ID代替App Engine用户ID.(此ID通常被视为Google+ ID,但这在许多服务中都是一致的.)

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

  • 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日撰写的此范围列表.)

与承载令牌情况下的App Engine用户ID类似,此Google个人资料ID将被丢弃endpoints.get_current_user(),它可用于两种令牌.

get_google_plus_user_id() 方法,其是一部分appengine-picturesque-python样本碎片的一个endpoints.get_current_user()辅助的方法来围绕保持这个数据,并允许您使用此值,而无需重复用于从请求验证承载或ID令牌的昂贵的网络呼叫.

  • Java中是否还有一个工作示例(例如,针对另一种解决方法和更好的替代方案)? (3认同)
  • 我正在尝试将已登录的用户与我的应用中的用户相关联.使用`email()`将不起作用,因为我的应用程序不知道用户何时更改其电子邮件.检索某种`user_id`保证我知道该用户是谁,因为`user_id`永远不会改变.在考虑像MightyText这样的其他应用时,我的困惑就出现了.当我登录我的数据存储区时,他们如何进行用户身份验证和关联?我知道他们使用oauth(Chrome源代码),所以他们可能有RPC开销吗? (2认同)
  • @gabriel你能提供一个代码片段来说明你如何在java中使用它吗? (2认同)