Подтвердить что ты не робот

Аутентификация вашего клиента в облачных конечных точках без входа в учетную запись Google

Я проводил обширные исследования о том, как аутентифицировать своего клиента (Android, iOS, веб-приложение) с облачными конечными точками, не требуя от пользователя использовать их учетную запись в учетной записи Google, как документация показывает вам.

Причиной этого является то, что я хочу защитить свой API или "заблокировать его" только для моих указанных клиентов. Иногда у меня будет приложение, у которого нет входа пользователя. Мне бы не хотелось приставать к моему пользователю, чтобы теперь войти в систему, чтобы мой API был безопасным. Или в другое время, я просто хочу управлять своими собственными пользователями, например, на веб-сайте, а не использовать Google+, Facebook или другое удостоверение подлинности для входа в систему.

Для начала позвольте мне сначала показать, как вы можете аутентифицировать свое Android-приложение с помощью API Cloud Endpoints с помощью входа в учетные записи Google, как указано в documentation. После этого я покажу вам свои выводы и потенциальную область для решения, с которым мне нужна помощь.

(1) Укажите идентификаторы клиентов (clientIds) приложений, уполномоченных делать запросы к вашему серверу API, и (2) добавить параметр пользователя ко всем открытым методам, которые будут защищены авторизацией.

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 вызывается метод API в Asynctask, который должен пройти в переменной credential в 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 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 (подробнее о том, как сделайте это здесь).

Сервер Google App Engine получает ваш запрос и проверяет его. Если Клиент Android является одним из тех, что вы указали в нотации @Api, тогда сервер будет внедрять объект com.google.api.server.spi.auth.common.User в ваш метод API. Теперь вы должны проверить, является ли этот объект User null или нет в вашем методе API. Если объект User null, вы должны выбросить исключение в свой метод, чтобы предотвратить его запуск. Если вы не сделаете эту проверку, ваш метод API будет выполнен (нет-нет, если вы пытаетесь ограничить доступ к нему).

Вы можете получить свой ANDROID_CLIENT_ID, перейдя в Google Developers Console. Там вы указываете имя пакета вашего приложения Android и SHA1, который генерирует для вас идентификатор клиента Android для использования в аннотации @Api (или помещает его в класс Constants, как указано выше для удобства использования).

Я провел некоторое тщательное тестирование со всем вышеперечисленным, и вот что я нашел:

Если вы укажете фиктивный или недопустимый Android clientId в аннотации @Api, объект User будет null в вашем методе API. Если вы выполняете проверку на if (user == null) throw new UnauthorizedException("User is Not Valid");, тогда ваш метод API не будет запущен.

Это удивительно, потому что похоже, что в облачных конечных точках происходит некоторая проверка за кулисами, которые проверяют, действительно ли Android ClientId действителен или нет. Если он недействителен, он не вернет объект User, даже если конечный пользователь зарегистрировался в своей учетной записи Google и GoogleAccountCredential был действительным.

Мой вопрос: кто-нибудь знает, как я могу проверить этот тип проверки ClientId самостоятельно в своих методах Cloud Endpoints? Может ли эта информация передаваться в HttpHeader например?

Другим введенным типом в Cloud Endpoints является javax.servlet.http.HttpServletRequest. Вы можете получить такой запрос в своем методе API:

@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;
            }

        }  

Но я не уверен, есть ли необходимая информация или как ее получить.

Конечно, где-то должны быть некоторые данные, которые сообщают нам, является ли Клиент авторизованным и указанным в @Api clientIds.

Таким образом, вы можете заблокировать свой API в своем приложении для Android (и, возможно, других клиентах), даже не прибегая к тому, чтобы ваши конечные пользователи приходили на вход (или просто создали свое собственное простое имя пользователя + пароль).

Чтобы все это работало, вы должны пройти в null в третьем аргументе вашего Builder следующим образом:

MyApi.Builder builder = new MyApi.Builder(AndroidHttp.newCompatibleTransport(),                           новый AndroidJsonFactory(), null)

Затем в вашем методе API извлеките, был ли вызов получен от аутентифицированного клиента, и либо выбросил исключение, либо выполнил любой код, который вы хотели.

Я знаю, что это возможно, потому что при использовании GoogleAccountCredential в Builder, как-то Cloud Endpoints знает, пришел ли вызов от аутентифицированного клиента, а затем либо введет его объект User в метод API, либо нет основываясь на этом.

Может ли эта информация быть в заголовке или теле? Если да, то как я могу получить его, чтобы позже проверить, есть ли это в моем методе API?

Примечание. Я прочитал другие сообщения по этой теме. Они предлагают способы передать свой токен аутентификации - это нормально, но ваш .apk по-прежнему не будет защищен, если кто-то его декомпилирует. Я думаю, что если моя гипотеза будет работать, вы сможете заблокировать API Cloud Endpoints для клиента без каких-либо логинов.

Пользовательская аутентификация для конечных точек Google Cloud (вместо OAuth2)

Аутентификация моего приложения " к облачным конечным точкам Google не является "пользователем"

Конечные точки Google Cloud без учетных записей Google

EDIT: Мы использовали Gold Support для Google Cloud Platform и много раз разговаривали со своей службой поддержки в течение нескольких недель. Это их окончательный ответ для нас:

"К сожалению, мне не повезло. Я спросил об этом команда и проверили всю документацию. Похоже, использование OAuth2 это ваш единственный вариант. Причина в том, что серверы конечных точек обрабатывают перед тем, как он достигнет вашего приложения. Это означает, что вы не иметь возможность разрабатывать собственный поток аутентификации и получать результаты очень похоже на то, что вы видели с помощью токенов.

Я был бы рад предоставить вам запрос на функцию. Если бы ты мог предоставить немного больше информации о том, почему поток OAuth2 не работа для ваших клиентов, я могу поместить остальную информацию вместе и отправьте его менеджеру продукта".

(хмурое лицо) - однако, возможно, это все еще возможно?

4b9b3361

Ответ 1

Я реализовал Endpoint Auth с помощью настраиваемого заголовка "Авторизация", и он работает отлично. В моем случае этот токен устанавливается после входа в систему, но должен работать все равно с вашим приложением. Проверьте свои тесты, потому что значение должно быть там. Способ получения этого заголовка действительно:

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

Вы можете сделать еще один шаг и определить свои собственные реализации Authenticator и применить его к своим безопасным вызовам API.

Ответ 2

Столкнувшись с той же проблемой, чтобы найти решение для безопасного вызова моего API из моих конечных точек, без использования учетной записи Google. Мы не можем декомпилировать приложение IOS (Bundle), но декомпилировать приложение для Android настолько просто.

Решение, которое я нашел, не является совершенным, но делает работу довольно хорошо:

  • В андроидном приложении APP я просто создаю константную переменную String с именем APIKey с простым контентом (например, "helloworld145698" ).
  • Затем я зашифрую его с помощью sha1, next md5 и, наконец, sha1 (порядок и частота шифрования до вас) и сохраните переменную в SharedPref (для Android) в частном режиме (выполните это действие на случайном классе в вашем приложении) Этот результат зашифрован, я разрешаю на моем бэкэнде!
  • На моем сервере я просто добавляю параметр (названный token для примера) для каждого запроса

Пример:

 @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. В андроиде активен ProGuard для обфускации вашего кода. Это будет действительно нечитаемым для тех, кто пытался декомпилировать ваше приложение (Reverse engineering действительно хардкор).

Не идеальное безопасное решение, но оно работает, и действительно действительно (действительно) сложно найти настоящий ключ API для тех, кто пытается прочитать ваш код после декомпиляции.

Ответ 3

Таким образом, у вас нет какой-либо пользовательской информации, но просто хотите убедиться, что только ваше приложение может общаться с вашим бэкэнд... Это то, что я думаю,

изменить

@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})
{
...
}

to

@Api(name = "myApi", version = "v1",
         namespace = @ApiNamespace(ownerDomain = "${endpointOwnerDomain}",
         ownerName = "${endpointOwnerDomain}",
         packagePath="${endpointPackagePath}"),
         scopes = {Constants.EMAIL_SCOPE}, 
         clientIds = {Constants.ANDROID_CLIENT_ID},
         audiences = {Constants.ANDROID_AUDIENCE})

{
...
}

Идентификатор клиента создается из подписи вашего приложения. Он не может быть воспроизведен. Если вы разрешаете своим конечным точкам принимать запросы из приложения Android, ваша проблема будет решена.

Скажите, если это сработает.