Google Endpoints API + Chrome 扩展为 endpoints.get_current_user().user_id() 返回 None [英] Google Endpoints API + Chrome Extension returns None for endpoints.get_current_user().user_id()

本文介绍了Google Endpoints API + Chrome 扩展为 endpoints.get_current_user().user_id() 返回 None的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am developing Google App Engine application written in Python and using Endpoints API. In conjunction, I am writing a Chrome Extension to interact with the Endpoints API. I've been running into lots of issues with the Endpoints API and authorization. Currently, here is my setup:

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)

Google API 控制台

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

Google API Console

The JS origins include: chrome-extensions://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.".

Result

Now that I have shown the main chunk of my code (note, I had to change it a bit to make sense without uploading entire code), if I go to the Google API Explorer and run that method, I get a 200 response. If I run it in the Chrome Extension, I get a 404 code with the message "Your user id was not found.".

推荐答案

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

It's unclear why/when this ever results in a 200; it should not. As mentioned in Function User.getUserId() in Cloud endpoint api returns null for a user object that is not null, this is a known issue.

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

The user_id will never be populated in the result returned from endpoints.get_current_user(). A workaround exists: by storing the user object in the datastore and then retrieving it (with a new get, if you are using ndb), the user_id() value will be populated.

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

You should strongly consider using the Google Profile ID associated with the account instead of the App Engine User ID.

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

endpoints is meant to be used with both Bearer tokens and ID tokens (for Android). ID tokens are a special type of JWT (JSON web token) signed in conjunction with on device crypto. As a result, parsing the user from these tokens can only determine the information encoded in that token (see Cloud endpoints oauth2 error for more info about that).

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

Since these tokens are minted by a generic Google Auth provider (OAuth 2.0) outside of App Engine, the App Engine User ID is not known/shared by this service. As a result, it is never possible to populate the user_id() when an ID token is used to sign the request.

当使用标准的不记名令牌(这对您的 Chrome 应用程序很好)时,App Engine OAuth API 被使用.当 OAuth API 调用

When a standard Bearer token is used (which would be fine for your Chrome application), the App Engine OAuth API is used. When the OAuth API calls

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()WILL会被设置,但是,用户值不会保留在endpoints.get_current_user,只有 email 和 auth 域.

method is called. This makes an RPC to a shared App Engine layer which provides a service that is able to get the current user from the token. In this case, the user_id() of the returned user WILL be set, however, the user value is not kept around for endpoints.get_current_user, only the email and the auth domain are.

oauth.get_current_user() 调用只是昂贵的IF,它使 RPC._maybe_call_get_oauth_user 方法存储上次调用的值,因此第二次调用 oauth.get_current_user() 不会产生网络/速度开销,除了几纳秒来查找来自 Python dict 的值.

The oauth.get_current_user() call is only expensive IF it makes the RPC. The _maybe_call_get_oauth_user method stores the value from the last call, so calling oauth.get_current_user() a second time will incur no network/speed overhead other than the few nanoseconds to lookup a value from a Python dict.

这很重要,因为 endpoints.get_current_user() 使用对 oauth.get_current_user() 的调用来确定 Bearer 令牌用户,因此如果您想再次调用它,您会担心该性能.

This is crucial because endpoints.get_current_user() uses a call to oauth.get_current_user() to determine the Bearer token user, so if you wanted to call it again, you'd worry about that performance.

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

If you know you'll never be using ID tokens or can easily determine those situations, you could change your code to just call both:

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 之一.

NOTE: We still must call endpoints.get_current_user() because it always makes sure that our token has been minted only for one of the specific scopes we've allowed and for one of the specific client IDs we have whitelisted to talk to our application.

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

NOTE: The value known_scope will vary depending on which of your possible scopes matches the token. Your list of scopes will be looped through in one of the endpoints.get_current_user() helper methods, and if this succeeds, the final matching scope will be stored as os.getenv('OAUTH_LAST_SCOPE'). I would strongly recommend using this value for known_scope.

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

As mentioned, the App Engine User ID simply can't be implied from an ID token (at current), however, the Google Profile ID can be used instead of the App Engine User ID. (This ID is often seen as the Google+ ID, though this is consistent across many services.)

要确保此值与您的 Bearer OR ID 令牌相关联,请确保您还请求与 userinfo API:

To make sure this value is associated with your Bearer OR ID tokens, make sure you also request one of the non-userinfo.email scopes associated with the userinfo API:

  • 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 日撰写本文时的当前范围列表.)

(This list of scopes current as of this writing on May 20, 2013.)

与不记名令牌案例中的 App Engine 用户 ID 类似,此 Google 个人资料 ID 被 endpoints.get_current_user() 丢弃,它可用于两者各种代币.

Similarly as with the App Engine User ID in the Bearer token case, this Google Profile ID is discarded by endpoints.get_current_user(), BUT it is available for both kinds of tokens.

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

The get_google_plus_user_id() method which is part of the appengine-picturesque-python sample patches one of the endpoints.get_current_user() helper methods to keep this data around and allows you to use this value without having to repeat the expensive network calls used to validate the Bearer or ID token from the request.

这篇关于Google Endpoints API + Chrome 扩展为 endpoints.get_current_user().user_id() 返回 None的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆