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扩展与终端API交互。我已经遇到了许多有关Endpoints API和授权的问题。目前,这是我的设置:

端点API(Python)



  from google.appengine.ext从protorpc导入endpoints 
从protorpc导入message_types
导入远程

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):
$ b $ @ 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()
如果current_user是None:
raise endpoints.UnauthorizedException('您必须首先进行身份验证。 ')
如果curre nt_user.user_id()是None:
raise endpoints.NotFoundException(Your user id was found。)

return DeviceListResponse(devices = [])#Hypothetically return devices

api_service = endpoints.api_server([MyApi],restricted = False)



Google API控制台



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


$ b

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;
$ b $ // Helper方法登录到控制台
函数l(o){console.log(o);}

函数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(令牌);
l(已成功验证加载设备列表);
gapi.client。 my_api.user.device.list({})。execute(function(resp){
if(resp.code){
l(来自设备列表的响应:+ resp.message);
}
l(resp);
});
}
});
});
}


//这个get在页面和js库加载时调用。
函数jsClientLoad(){
l(JS Client Libary loaded,Now loading my_api and oauth2 APIs。);
var apisToLoad;
var callback = function(){
if(--apisToLoad == 0){
l(已加载API)
oauthSignin(true);
} else {
l(等待+ apisToLoad +API+(apisToLoad> 1?s:)+加载。
}
}

apisToLoad = 2; //必须匹配gapi.client.load()的调用次数
gapi.client.load('my_api','v1',callback,apiRoot);
gapi.client.load('oauth2','v2',callback);

$ / code>



结果



现在我已经展示了我的代码的主要部分(注意,如果不上传整个代码,我必须稍微修改一下),如果我访问Google API Explorer并运行该方法,则会得到200响应。如果我在Chrome扩展程序中运行它,则会收到一条404代码,并显示您的用户标识未找到消息。 解决方案

<目前还不清楚为什么/何时会导致 200 ;它不应该。正如 Cloud端点中的Function User.getUserId()所述,api为非空的用户对象返回null ,这是一个< a href =https://code.google.com/p/googleappengine/issues/detail?id=8848 =nofollow noreferrer>已知问题。

TLDR;



user_id 永不填入从 endpoints.get_current_user()返回的结果。解决方法是:将用户对象存储在数据存储中,然后检索它(如果使用的是 ndb ),那么 user_id ()值将被填充。



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

历史/说明:



端点与持票人令牌和身份证令牌(适用于Android)一起使用。 ID令牌是与设备crypto一起签名的特殊类型的JWT(JSON Web令牌)。因此,从这些令牌解析用户只能确定在该令牌中编码的信息(有关更多信息,请参阅云端点oauth2错误由于这些令牌是由App Engine之外的通用Google Auth提供者(OAuth 2.0)提供的,因此App Engine用户ID不知道/由这项服务。因此,当使用ID令牌来签署请求时, 永远不可填充 user_id()。 b
$ b

如果使用标准的持票人标记(这对您的Chrome应用程序来说可能很好),则应用程序引擎 OAuth API 。当OAuth API调用时

  oauth.get_current_user(some_scope)

(其中 oauth google.appengine.api.oauth ),

  oauth.oauth_api._maybe_call_get_oauth_user(_scope =无)

方法。这使一个 RPC 到共享的App Engine层,它提供了一个服务,能够获得当前用户来自令牌。在此情况中,返回的用户 WILL user_id()被设置,但用户值不是 endpoints.get_current_user ,只有电子邮件和授权域名是。



另一个解决方法:



oauth.get_current_user()调用只是很昂贵的 IF 它使得RPC成为可能。 _maybe_call_get_oauth_user 方法存储最后一次调用的值,因此第二次调用 oauth.get_current_user()不会产生no网络/速度开销,而不是几纳秒来从Python中查找值 dict



这很关键,因为 endpoints.get_current_user()使用对 oauth.get_current_user()的调用来确定持证者令牌用户,所以如果您如果你知道你永远不会使用ID标记或者可以很容易地确定这些情况,你可以改变它您的代码只需调用两者:

  endpoints_user = endpoints.get_current_user()
如果endpoints_user是None:
提高endpoints.UnauthorizedException(...)

oauth_user = oauth.get_current_user(known_scope)
如果oauth_user是None或oauth_user.user_id()是None:
#这应该永远不会发生
提升结束points.NotFoundException(...)

注意:我们仍然必须致电 endpoints.get_current_user(),因为它总是确保我们的令牌只是针对我们允许的特定范围之一进行铸造,对于我们的特定客户端ID之一白名单与我们的应用程序交谈。


$ b :值 known_scope 在您的可能范围中的哪一个与令牌匹配。您的范围列表将在 endpoints.get_current_user() 助手方法,如果成功,最终的匹配范围将为存储为 os.getenv('OAUTH_LAST_SCOPE')。我强烈建议使用这个值作为 known_scope



更好的选择:



如前所述,App Engine用户标识不能从ID标记(当前)隐含,但 Google个人资料ID 可以用来代替App Engine用户ID。 (这个ID通常被视为Google+ ID,虽然这在许多服务中都是一致的。)

为确保此值与您的持有者相关 ID令牌,请确保您还请求与 userinfo.email 范围之一/apis-explorer/#p/oauth2/v2/oauth2.userinfo.getrel =nofollow noreferrer> 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日, 。
$ b 与持有人令牌情况下的App Engine用户ID类似,此Google配置文件ID由 endpoints.get_current_user()它可用于这两种标记。



get_google_plus_user_id() <一个href =https://github.com/GoogleCloudPlatform/appengine-picturesque-python/blob/833c784239ae97f3c78239ddf1fbb98346c96f4a/auth_util.py#L26 =nofollow noreferrer>方法,它是 appengine-picturesque-python 示例补丁 endpoints.get_current_user()辅助方法保留这些数据,并允许您使用此值,而不必重复用于验证承载或ID令牌的昂贵网络调用来自请求。


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:

Endpoints 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)

Google API Console

The JS origins include: 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);
}

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.".

解决方案

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.

TLDR;

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.

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

History/Explanation:

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).

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.

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)

(where oauth is google.appengine.api.oauth), the

oauth.oauth_api._maybe_call_get_oauth_user(_scope=None)

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.

Another Workaround:

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.

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.

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(...)

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.

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.

Better Alternative:

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.)

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

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

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.

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天全站免登陆