无需 Google 帐户登录即可向 Cloud Endpoints 验证您的客户端 [英] Authenticating your client to Cloud Endpoints without a Google Account login

查看:28
本文介绍了无需 Google 帐户登录即可向 Cloud Endpoints 验证您的客户端的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在广泛研究如何使用 Cloud Endpoints 对您的客户端(Android、iOS、Web 应用程序)进行身份验证无需您的用户使用他们的 Google 帐户登录文档 向您展示.

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.

这样做的原因是我想保护我的 API 或锁定"给我指定的客户.有时我会有一个没有用户登录的应用程序.我不想纠缠我的用户现在登录只是为了我的 API 是安全的.或者其他时候,我只想像在网站上一样管理我自己的用户,而不是使用 Google+、Facebook 或其他任何登录身份验证.

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) 指定被授权向您的 API 后端发出请求的应用程序的客户端 ID (clientIds) 和 (2) 向所有公开的方法添加一个 User 参数以受授权保护.

(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) 在 Android 中调用 Asynctask 中的 API 方法,确保在 Builder 中传入 credential 变量:

(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();
        }
    }

发生的事情是,在您的 Android 应用程序中,您首先显示了 Google 帐户选择器,将该 Google 帐户电子邮件存储在您共享的首选项中,然后将其设置为 GoogleAccountCredential 对象的一部分(有关如何执行此操作的更多信息此处).

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

Google App Engine 服务器接收您的请求并进行检查.如果 Android 客户端是您在 @Api 表示法中指定的客户端之一,则服务器将注入 com.google.api.server.spi.auth.common.User object 到您的 API 方法中.现在您有责任检查 User 对象是否为 null 或不在您的 API 方法中.如果 User 对象为 null,则应在方法中抛出异常以防止其运行.如果您不执行此检查,您的 API 方法将执行(如果您试图限制对它的访问,则为否).

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

您可以前往 Google Developers Console 获取您的 ANDROID_CLIENT_ID.在那里,您提供 Android 应用程序的包名称和 SHA1,它为您生成一个 android 客户端 ID,供您在 @Api 注释中使用(或将其放在类 Constants 就像上面为可用性指定的一样).

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

我对上述所有内容进行了一些广泛的测试,以下是我的发现:

如果您在 @Api 注释中指定了虚假或无效的 Android clientId,则 API 方法中的 User 对象将为 null.如果您正在检查 if (user == null) throw new UnauthorizedException("User is Not Valid"); 那么您的 API 方法将不会运行.

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.

这令人惊讶,因为似乎 Cloud Endpoints 中正在进行一些幕后验证,以检查 Android ClientId 是否有效.如果它无效,它不会返回 User 对象 - 即使最终用户登录到他们的 Google 帐户并且 GoogleAccountCredential 是有效的.

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.

我的问题是,有谁知道我如何在我的 Cloud Endpoints 方法中自行检查该类型的 ClientId 验证?例如,该信息可以在 HttpHeader 中传递吗?

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?

Cloud Endpoints 中的另一种注入类型是 javax.servlet.http.HttpServletRequest.你可以在你的 API 方法中得到这样的请求:

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.

当然某处必须有一些数据告诉我们客户端是否是授权的并在@Api clientIds中指定.

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

通过这种方式,您可以将 API 锁定到您的 Android 应用(以及可能的其他客户端),而无需纠缠您的最终用户登录(或只需创建您自己的简单用户名 + 密码登录).

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

为了使所有这些工作,您必须像这样在 Builder 的第三个参数中传入 null:

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)

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

然后在您的 API 方法中提取调用是否来自经过身份验证的客户端,然后抛出异常或运行您想要的任何代码.

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.

我知道这是可能的,因为在 Builder 中使用 GoogleAccountCredential 时,Cloud Endpoints 以某种方式知道调用是否来自经过身份验证的客户端,然后注入其User 对象是否基于 API 方法进入 API 方法.

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.

该信息会以某种方式出现在标题或正文中吗?如果是这样,我怎样才能得到它以便以后检查它是否存在于我的 API 方法中?

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?

注意:我阅读了有关此主题的其他帖子.他们提供了传递您自己的身份验证令牌的方法 - 这很好 - 但如果有人反编译,您的 .apk 仍然不安全.我认为,如果我的假设成立,您将无需任何登录即可将 Cloud Endpoints API 锁定到客户端.

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.

Google Cloud Endpoints 的自定义身份验证(而不是 OAuth2)

验证我的应用"到 Google Cloud Endpoints 而非用户"

没有 Google 帐户的 Google Cloud Endpoints

我们使用了 Google Cloud Platform 的金牌支持服务,并与他们的支持团队进行了数周的反复沟通.这是他们给我们的最终答案:

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:

不幸的是,我在这方面没有运气.我问过我的团队,并检查了所有文档.看起来像使用 OAuth2是您唯一的选择.原因是因为端点服务器处理在它到达您的应用程序之前进行身份验证.这意味着你不会能够开发自己的身份验证流程,并会得到结果就像你看到的令牌一样.

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

我很乐意为您提交功能请求.如果你可以提供更多关于为什么 OAuth2 流没有的信息为你的客户工作,我可以把剩下的信息一起提交给产品经理."

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?

推荐答案

我已经使用自定义标题授权"实现了端点身份验证,并且它工作得很好.在我的情况下,此令牌是在登录后设置的,但应该与您的应用程序完全相同.检查您的测试,因为值应该在那里.检索该标头的方法确实是:

I have implemented Endpoint Auth using a custom header "Authorization" and it works just fine. In my case this token is set after login but should work all the same with your app. Check your tests because the value should be there. The way to retrieve that header is indeed:

String Auth = req.getHeader("Authorization");

您可以更进一步,定义您自己的 Authenticator 并将其应用于您的安全 API 调用.

You could take it a step further and define your own implementations of an Authenticator and apply it to your secure API calls.

这篇关于无需 Google 帐户登录即可向 Cloud Endpoints 验证您的客户端的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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