验证您的客户端云端点没有一个谷歌帐户登录 [英] Authenticating your client to Cloud Endpoints without a Google Account login

查看:217
本文介绍了验证您的客户端云端点没有一个谷歌帐户登录的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在做关于如何验证您的客户端广泛的研究(Android版,iOS版,网络应用程序)与云端点的没有的要求用户使用他们的谷歌帐户登录时的文档告诉您。

这样做的原因是,我想保护我的API或锁定了只有我指定的客户。有时候我会有不具有一个用户登录的应用程序。我不想纠缠我的用户立即登录刚刚所以我的API是安全的。还是其他时候,我只是想管理自己的用户喜欢在网站上,并没有使用Google+,Facebook或任何其他人登录验证。

要开始,让我先告诉你可以作为的文档。从那以后,我会告诉你我的发现和我需要帮助解决的潜在领域。

(1)指定授权作出请求您的API后端和(2)添加用户参数的所有公开的方法的应用程序的客户端ID(的clientId)被授权保护。

 公共类常量{
      公共静态最后弦乐WEB_CLIENT_ID =1-web-apps.apps.googleusercontent.com;
      公共静态最后弦乐ANDROID_CLIENT_ID =2-android-apps.googleusercontent.com;
      公共静态最后弦乐IOS_CLIENT_ID =3-ios-apps.googleusercontent.com;
      公共静态最后弦乐ANDROID_AUDIENCE = WEB_CLIENT_ID;      公共静态最后弦乐EMAIL_SCOPE =htt​​ps://www.googleapis.com/auth/userinfo.email;
    }
进口com.google.api.server.spi.auth.common.User; //进口为用户对象    @Api(NAME =myApi,版本=V1,
         命名空间= @ApiNamespace(ownerDomain =$ {} endpointOwnerDomain
         OWNERNAME =$ {} endpointOwnerDomain
         packagepath的=$ {endpointPackagePath}),
         范围= {} Constants.EMAIL_SCOPE,
         的clientId = {Constants.WEB_CLIENT_ID,Constants.ANDROID_CLIENT_ID,
                      Constants.IOS_CLIENT_ID,
                      Constants.API_EXPLORER_CLIENT_ID},
                      观众= {} Constants.ANDROID_AUDIENCE)    公共类MyEndpoint {        / **一个简单的端点方法,它的名称和喜说,回* /
        @ApiMethod(NAME =sayHi的)
        公共MyBean的sayHi(@Named(name)的字符串名称,用户用户)抛出UnauthorizedException {
            如果(用户== NULL)抛出新UnauthorizedException(用户无效);
            MyBean响应=新MyBean();
            response.setData(你好,+姓名);            返回响应;
        }    }

(3)在Android中调用在AsyncTask的API方法,确保在来传递凭证生成器变量

 类EndpointsAsyncTask扩展的AsyncTask<对<语境,字符串>中太虚,字符串> {
        私有静态MyApi myApiService = NULL;
        私人上下文的背景下;        @覆盖
        保护字符串doInBackground(对<语境,字符串> ... PARAMS){
            凭据= GoogleAccountCredential.usingAudience(这一点,
            服务器:CLIENT_ID:1-web-app.apps.googleusercontent.com);
            credential.setSelectedAccountName(settings.getString(preF_ACCOUNT_NAME,NULL));
            如果(myApiService == NULL){//只有这样做一次
                MyApi.Builder建设者=新MyApi.Builder(AndroidHttp.newCompatibleTransport()
                        新AndroidJsonFactory(),凭据)
                    反对地方devappserver运行//选项
                    // - 在Android模拟器10.0.2.2为localhost的IP地址
                    // - 反对地方devappserver运行时关闭COM pression
                    .setRootUrl(HTTP://<您的应用引擎项目-ID-这里> / _ah / API /)
                    .setGoogleClientRequestInitializer(新GoogleClientRequestInitializer(){
                        @覆盖
                        公共无效初始化(AbstractGoogleClientRequest<> abstractGoogleClientRequest)抛出IOException
                            abstractGoogleClientRequest.setDisableGZipContent(真);
                        }
                    });
                    //为devappserver结束选项                myApiService = builder.build();
            }            上下文=参数[0]。首先,
            字符串名称=参数[0]。第二;            尝试{
                返回myApiService.sayHi(名称).execute()的getData();
            }赶上(IOException异常五){
                返回e.getMessage();
            }
        }        @覆盖
        保护无效onPostExecute(字符串结果){
            Toast.makeText(上下文,结果,Toast.LENGTH_LONG).show();
        }
    }

正在发生的事情是,在你的Andr​​oid应用程序,你都显示了谷歌帐户选择器首先,存储在您谷歌帐户的电子邮件共享preferences,再后来其设置为 GoogleAccountCredential的一部分对象(对此处做更多信息)。

在谷歌应用程序引擎服务器接收请求,并检查它。如果Android客户端是您在 @Api 标记中指定的之一,那么服务器将注入 com.google.api.server。 spi.auth.common.User 对象到您的API方法。现在是你的责任,以检查是否是用户对象或里面没有你的API方法。如果用户对象,你应该在你的方法运行抛出异常prevent它。如果你不这样做检查,你的API方法将执行(一个没有没有,如果你想限制访问它)。

您可以进入您的谷歌开发者控制台你的 ANDROID_CLIENT_ID 。在那里,您提供Android应用程序的包名和产生你的一个Android客户端ID为你在你的 @Api 标注使用SHA1(或把它放在一个类常量像上面可用性指定)。

我已经做了所有上述和这里的一些广泛的测试是我发现:

如果您在 @Api 批注指定一个虚假或无效的Andr​​oid客户端ID,在用户对象将是在你的API方法。如果你是,如果(用户== NULL)正在为检查抛出新UnauthorizedException(用户无效); 那么你的API方法将无法运行。

这是令人惊讶的,因为它似乎有一些场景的验证云端点去上检查了Android客户端Id是否有效并不落后。如果它是无效的,它不会返回用户对象 - 即使最终用户登录到自己的谷歌帐户和 GoogleAccountCredential 是有效的。

我的问题是,没有人知道我可以为您在我的云端点方法对我自己的这种类型的客户端Id验证?难道这些信息在的HTTPHeader 中传递,例如?

在云的另一端点注入类型是 javax.servlet.http.HttpServletRequest 。你可以得到这样的请求在您的API方法:

  @ApiMethod(NAME =sayHi的)
            公共MyBean的sayHi(@Named(name)的字符串名称,HttpServletRequest的REQ)抛出UnauthorizedException {                字符串验证= req.getHeader(授权); //总是空根据我的测试
                MyBean响应=新MyBean();
                response.setData(你好,+姓名);                返回响应;
            }        }

但我不知道,如果必要的信息是否有或如何得到它。

当然,地方的必须有一些数据告诉我们,如果客户端授权,并指定一个在 @Api 的clientId

这样,你可以在你的API锁定到你的Andr​​oid的应用程序(和潜在的其他客户端),而不必纠缠你的最终用户登录(或只是创建自己的简单的用户名+密码登录)。

有关这一切的工作,虽然,你会在来传递你的生成器是这样的:

MyApi.Builder建设者=新MyApi.Builder(AndroidHttp.newCompatibleTransport()
                            新AndroidJsonFactory(),NULL)

然后在您的API方法提取呼叫是否来自经过认证的客户端来了,无论是和抛出一个异常,或运行任何code你想。

我知道,因为使用时,这是可能的一个 GoogleAccountCredential 生成器,不知何故云端点知道与否电话来自经过认证的客户端来,然后要么注入了用户对象到API方法或不基于这一点。

莫非信息必须在头或身体不知何故?如果是这样,我该怎么把它弄出来以后检查,如果它的存在与否在我的API方法?

请注意:我看了关于这一主题的其他职位。他们提供的方式在自己的认证令牌传递 - 这是好的 - 但如果有人反编译它的apk文件仍然是不安全的。我想,如果我的假设的作品,你就能以锁定您的云API端点到客户端没有任何登录。

<一个href=\"http://stackoverflow.com/questions/16970327/custom-authentication-for-google-cloud-endpoints-instead-of-oauth2?rq=1\">Custom身份验证谷歌云端点(代替的OAuth2)

<一个href=\"http://stackoverflow.com/questions/20396577/authenticate-my-app-to-google-cloud-endpoints-not-a-user\">Authenticate我的&QUOT;应用&QUOT;以谷歌的云终端不是一个&QUOT;用户QUOT;

<一个href=\"http://stackoverflow.com/questions/19224728/google-cloud-endpoints-without-google-accounts\">Google云端点没有谷歌帐户

编辑:
我们用黄金支持的谷歌云计算平台,并已谈来谈去与他们的支持团队周。这是他们最后的答案我们:


  

不幸的是,我没有在这个幸运的话。我问过我的身边
  团队,并检查了所有的文件。它看起来像使用的OAuth2
  是你唯一的选择。究其原因是因为端点服务器处理
  之前认证达到您的应用程序。这意味着你不会
  能够开发自己的认证流程,并会得到结果
  就像你用令牌看到。


  
  

我会很乐意提交给你一个功能请求。如果你可以
  提供为什么OAuth2流程并不多一点信息
  为您的客户工作,我可以把其余的信息
  一起提交给产品经理。


(frowny面) - 但是,也许它仍然是可能的?


解决方案

面对同样的问题找到一个解决方案,从我的端点安全地调用我的API,不使用谷歌帐户。我们不能反编译iOS应用程序(包),但反编译Android应用就是这么简单。

我找到了解决办法是不完美的,但做的工作pretty好:


  1. 在Android应用程序,我只是创建一个常量字符串变量,命名APIKey,用简单的内容(例如,helloworld145698)

  2. 然后我SHA1,接下来的MD5,最后SHA1(Order和加密给你的频率)进行加密和共享preF(Android设备)变量存储在 私模 (请在一个随机类​​这个动作在你的应用程序)的这是这个结果我的加密授权在我的后端!

  3. 在我的后台,我只是在每次请求添加一个参数(名为为例令牌)

示例:

  @ApiMethod(NAME =sayHi的)
    公共无效的sayHi(@Named(name)的字符串名称,@Named(令牌)字符串令牌){    如果(令牌== tokenStoreOnAPIServer){
         //允许它
    }其他{
         //拒绝它和打印错误
    }}

<醇开始=4>

  • 在Android上,作为模糊的code活跃的ProGuard 。这将是任何人谁试图反编译你的应用程序确实无法读取(逆向工程是真正的铁杆)

  • 不是完美的安全解决方案,但它的工作原理,这将是真的(真正)很难找到的人谁试图读取反编译后的code真正的API密钥。

    I have been doing extensive research on how to authenticate your client (Android, iOS, web-app) with Cloud Endpoints without requiring your user to use their Google account login the way the documentation shows you.

    The reason for this is that I want to secure my API or "lock it down" to only my specified clients. Sometimes I will have an app that does not have a user login. I would hate to pester my user to now sign in just so my API is secure. Or other times, I just want to manage my own users like on a website and not use Google+, Facebook, or whatever else login authentication.

    To start, let me first show the way you can authenticate your Android app with your Cloud Endpoints API using the Google Accounts login as specified in the documentation. After that I will show you my findings and a potential area for a solution which I need help with.

    (1) Specify the client IDs (clientIds) of apps authorized to make requests to your API backend and (2) add a User parameter to all exposed methods to be protected by authorization.

    public class Constants {
          public static final String WEB_CLIENT_ID = "1-web-apps.apps.googleusercontent.com";
          public static final String ANDROID_CLIENT_ID = "2-android-apps.googleusercontent.com";
          public static final String IOS_CLIENT_ID = "3-ios-apps.googleusercontent.com";
          public static final String ANDROID_AUDIENCE = WEB_CLIENT_ID;
    
          public static final String EMAIL_SCOPE = "https://www.googleapis.com/auth/userinfo.email";
        }
    
    
    import com.google.api.server.spi.auth.common.User; //import for the User object
    
        @Api(name = "myApi", version = "v1",
             namespace = @ApiNamespace(ownerDomain = "${endpointOwnerDomain}",
             ownerName = "${endpointOwnerDomain}",
             packagePath="${endpointPackagePath}"),
             scopes = {Constants.EMAIL_SCOPE}, 
             clientIds = {Constants.WEB_CLIENT_ID, Constants.ANDROID_CLIENT_ID,
                          Constants.IOS_CLIENT_ID,
                          Constants.API_EXPLORER_CLIENT_ID},
                          audiences = {Constants.ANDROID_AUDIENCE})
    
        public class MyEndpoint {
    
            /** A simple endpoint method that takes a name and says Hi back */
            @ApiMethod(name = "sayHi")
            public MyBean sayHi(@Named("name") String name, User user) throws UnauthorizedException {
                if (user == null) throw new UnauthorizedException("User is Not Valid");
                MyBean response = new MyBean();
                response.setData("Hi, " + name);
    
                return response;
            }
    
        } 
    

    (3) In Android call the API method in an Asynctask making sure to pass in the credential variable in the Builder:

    class EndpointsAsyncTask extends AsyncTask<Pair<Context, String>, Void, String> {
            private static MyApi myApiService = null;
            private Context context;
    
            @Override
            protected String doInBackground(Pair<Context, String>... params) {
                credential = GoogleAccountCredential.usingAudience(this,
                "server:client_id:1-web-app.apps.googleusercontent.com");
                credential.setSelectedAccountName(settings.getString(PREF_ACCOUNT_NAME, null));
                if(myApiService == null) {  // Only do this once
                    MyApi.Builder builder = new MyApi.Builder(AndroidHttp.newCompatibleTransport(),
                            new AndroidJsonFactory(), credential)
                        // options for running against local devappserver
                        // - 10.0.2.2 is localhost's IP address in Android emulator
                        // - turn off compression when running against local devappserver
                        .setRootUrl("http://<your-app-engine-project-id-here>/_ah/api/")
                        .setGoogleClientRequestInitializer(new GoogleClientRequestInitializer() {
                            @Override
                            public void initialize(AbstractGoogleClientRequest<?> abstractGoogleClientRequest) throws IOException {
                                abstractGoogleClientRequest.setDisableGZipContent(true);
                            }
                        });
                        // end options for devappserver
    
                    myApiService = builder.build();
                }
    
                context = params[0].first;
                String name = params[0].second;
    
                try {
                    return myApiService.sayHi(name).execute().getData();
                } catch (IOException e) {
                    return e.getMessage();
                }
            }
    
            @Override
            protected void onPostExecute(String result) {
                Toast.makeText(context, result, Toast.LENGTH_LONG).show();
            }
        }
    

    What is happening is that in your Android app you are showing the Google account picker first, storing that Google account email in you shared preferences, and then later setting it as part of the GoogleAccountCredential object (more info on how to do that here).

    The Google App Engine server receives your request and checks it. If the Android Client is one of the ones you specified in the @Api notation, then the server will inject the com.google.api.server.spi.auth.common.User object into your API method. It is now your responsibility to check if that User object is null or not inside your API method. If the User object is null, you should throw an exception in your method to prevent it from running. If you do not do this check, your API method will execute (a no-no if you are trying to restrict access to it).

    You can get your ANDROID_CLIENT_ID by going to your Google Developers Console. There, you provide the package name of your Android App and the SHA1 which generates for you an android client id for you to use in your @Api annotation (or put it in a class Constants like specified above for usability).

    I have done some extensive testing with all of the above and here is what I found:

    If you specify a bogus or invalid Android clientId in your @Api annotation, the User object will be null in your API method. If you are doing a check for if (user == null) throw new UnauthorizedException("User is Not Valid"); then your API method will not run.

    This is surprising because it appears there is some behind the scenes validation going on in Cloud Endpoints that check whether the Android ClientId is valid or not. If it is invalid, it won't return the User object - even if the end user logged in to their Google account and the GoogleAccountCredential was valid.

    My question is, does anyone know how I can check for that type of ClientId validation on my own in my Cloud Endpoints methods? Could that information be passed around in an HttpHeader for example?

    Another injected type in Cloud Endpoints is the javax.servlet.http.HttpServletRequest. You can get the request like this in your API method:

    @ApiMethod(name = "sayHi")
                public MyBean sayHi(@Named("name") String name, HttpServletRequest req) throws UnauthorizedException {
    
                    String Auth = req.getHeader("Authorization");//always null based on my tests
                    MyBean response = new MyBean();
                    response.setData("Hi, " + name);
    
                    return response;
                }
    
            }  
    

    But I am not sure if the necessary information is there or how to get it.

    Certainly somewhere there must be some data that tells us if the Client is an authorized and specified one in the @Api clientIds.

    This way, you could lock-down your API to your Android app (and potentially other clients) without ever having to pester your end users to log in (or just create your own simple username + password login).

    For all of this to work though, you would have to pass in null in the third argument of your Builder like this:

    MyApi.Builder builder = new MyApi.Builder(AndroidHttp.newCompatibleTransport(), new AndroidJsonFactory(), null)

    Then in your API method extract whether or not the call came from an authenticated client, and either throw an exception or run whatever code you wanted to.

    I know this is possible because when using a GoogleAccountCredential in the Builder, somehow Cloud Endpoints knows whether or not the call came from an authenticated client and then either injects its User object into the API method or not based on that.

    Could that information be in the header or body somehow? If so, how can I get it out to later check if it is there or not in my API method?

    Note: I read the other posts on this topic. They offer ways to pass in your own authentication token - which is fine - but your .apk will still not be secure if someone decompiles it. I think if my hypothesis works, you will be able to lock-down your Cloud Endpoints API to a client without any logins.

    Custom Authentication for Google Cloud Endpoints (instead of OAuth2)

    Authenticate my "app" to Google cloud endpoints not a "user"

    Google Cloud Endpoints without Google Accounts

    EDIT: We used Gold Support for the Google Cloud Platform and have been talking back and forth with their support team for weeks. This is their final answer for us:

    "Unfortunately, I haven't had any luck on this. I've asked around my team, and checked all of the documentation. It looks like using OAuth2 is your only option. The reason is because the endpoint servers handle the authentication before it reaches your app. This means you wouldn't be able to develop your own authentication flow, and would get results much like what you were seeing with the tokens.

    I would be happy to submit a feature request for you. If you could provide a little more information about why the OAuth2 flow doesn't work for your customers, I can put the rest of the information together and submit it to the product manager."

    (frowny face) - however, maybe it is still possible?

    解决方案

    Faced the same problem to find a solution to call my API safely from my endpoints, without using Google Account. We can't decompile an IOS App (Bundle), but decompile an Android App is so simple..

    The solution I found is not perfect but do the job pretty good:

    1. On android APP, I just create an constant String variable, named APIKey, with simply content (For example "helloworld145698")
    2. Then I encrypt it with sha1, next md5, and finally sha1 (Order and frequency of encryption up to you) and store the variable on SharedPref (For Android) in private mode (Do this action on an random class in your App) It's this result encrypted I authorize on my Backend !
    3. On my backend, I just add a parameter (named token for exemple) on every request

    Example:

     @ApiMethod(name = "sayHi")
        public void sayHi(@Named("name") String name, @Named("Token") String token) {
    
        if (token == tokenStoreOnAPIServer) {
             //Allow it
        } else {
             //Refuse it and print error
        } 
    
    }
    

    1. On android, active ProGuard for obfuscated your code. It will be really unreadable for anyone who tried to decompile your app (Reverse engineering is really hardcore)

    Not THE perfect secure solution, but it works, and it will be really really (really) difficult to find the real API key for anyone who try to read your code after decompilation.

    这篇关于验证您的客户端云端点没有一个谷歌帐户登录的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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