Большинствопрограмм на android подразумевают работу с удаленным сервером. Они отправляют ему запросы, а в ответ получают какие-то данные, например, если Вы пишете игру, таким образом может быть загружена и обновлена таблица рекордов. При разработке серверной части перед программистами в полной рост встает проблема идентификации приложения и пользователя, который обратился за данными. Довольно часто для коммуникации с сервером используется протокол HTTP. Поэтому у любого мало-мальски понимающего хакера не возникнет труда самостоятельно составить POST запрос и подменить данные.
Google Play services с недавнего времени позволяет легко решить эту проблему. Для Android 2.2 и выше идентификация пользователя может осуществляться по данным о его Google аккаунте. Предлагаемое решение представляет собой многошаговый процесс. Если не вдаваться в подробности, то работа с сервисом выгладит следующим образом: вы используете классGoogleAuthUtilкоторый обращается к Google Play services и возвращает Вам строку, называемую "ID Token" или идентификационной меткой. Затем вы отправляете эту метку на свой сервер, и он, связавшись с Google, может выяснить какое приложение и какой пользователь пытаются получить данные. На серверной стороне общение с Google происходит через App Engine Cloud Endpoints. Подробнее об этой технологии можно прочитатьздесь.
Теперь, когда логика процесса понятна, давайте рассмотрим его более подробно.
Для регистрации своего приложения Вам придется воспользоватьсяGoogle API Console. Нужно будет создать новый проект, при этом совсем не обязательно тратить время на придумывание понятного человеку названия и разработку красивого графического оформления этого проекта. Вы можете также настроить и использовать этот проект для доступа к большому числу различных Google API. Серьезно подумате, прежде чем дать доступ к управлению этим проектом другим членам своей комманды, поскольку безответственный человек с правами администратора может стать причиной больших проблем в будущем.
Для проекта потребуется создать дваOAuth 2.0 "Client ID" - для серверной части и для Android клиента. Серверный "Client ID" не требует никакой настройки, поскольку от него нам понадобится только строка, которая будет выглядеть как-то так"9414861317621.apps.googleusercontent.com".
С Client ID для Android клиента все немного сложнее. Нужно задать package name и подпись-сертификат. Package name задается также, как и в Вашем Android проекта. Его можно посмотреть в самом верху AndroidManifest.xml. Package name обычно выглядит как-то так: com.example.identity.
Чтобы получить подпись-сертификат для вашего приложения, нужно выполнить следующую команду в консоли:
$ keytool -exportcert -alias <your-key-name> -keystore <your-key-store-file> -v -list
Затем нужно скопировать октет "SHA1" и вставить его в поле Developer Console. Опять же в результате будет создана Client ID строка, которая нам потребуется в дальнейшем.
В своем Android приложении необходимо использовать класс GoogleAuthUtil для обращения к Google Play services. Нам понадобится вызвать метод getToken(email, scope), который собственно и вернет нам требуемую ID метку. В качестве параметра scope нужно использовать строку вида
audience:server:client_id:X
где X - Client ID вашего сервера. Для рассмотренного выше примера это строка будет иметь вид: "audience:server:client_id:9414861317621.apps.googleusercontent.com".
Обычно, когда вы пытаетесь выполнить OAuth авторизацию на сервере, пользователю показывается окно, где он должен согласиться на передачу своих данных. В данном случае система глядя на параметр scope понимает, что сервер и ваше Android приложение входят в один и тот же проект, поэтому никаких лишних вопросов не задается и метка возвращается автоматически.
Когда в приложение потребуется получить доступ к серверу, вы должны отправить ему ID метку. Проще всего это сделать в POST запросе, добавив ее в URL параметры. Нужно понимать, что обычное HTTP соединение легко прослушивается, поэтому чтобы предотвратить перехват вашей ID метки необходимо использовать HTTPS.
Когда ваш сервер получил метку от Android приложения, он должен проверить ее. Для этого нужно выполнить два шага:
Чтобы проверить, что метка подписана с помощью ключа Google, нужно обратиться на страницу www.googleapis.com/oauth2/v1/certs, где Google публикует свои публичные ключи (которые, кстати регулярно меняются). Нужно убедиться, что полученная ID метка подписана с помощью одного из них. Существует десятки библиотек, которые реализуют эту задачу. Ниже будут показаны примеры на Java, Ruby и PHP. Библиотеки умеют кешировать ключи и по необходимости обновляют их. Поэтому проверка происходит очень быстро.
Что касается второй проверки - тут все немного сложнее. ID метка помимо собственно подписи несет в себе дополнительную информацию в формате JSON и большинство библиотек, осуществляющих проверку подписи также умеют распознавать и возвращать ее. Обычно результат выдается в виде хеша или словаря, имеющих следующие поля:aud,cidиemail.
Прежде всего, Вы должны проверить значение поляaud. Оно должно совпадать со значением параметраscope, который вы использовали в своем Android приложении при вызове методаgetToken. Это действительно важно. Если Вы не сделаете эту проверку, любой другой программист сможет использовать Ваш сервер из своего приложения.
Также вы можете проверить параметрcid. Он содержит Client ID Android приложения. Вообще у вас может быть несколько различных Android приложений, которые работают с сервером и проверка этого параметра позволит понять какое из них пытается получить информацию.
Самого пользователя можно идентифицировать по полюemail.
Подведем итог: Вы убедились, что подпись Google верная, вы однозначно идентифицировали обратившееся к серверу устройство и клиент. При этом информация имеет высокую степень защиты. Даже если запрос посылается со взломанного устройства, хакер не сможет претвориться другим устройством, поскольку аутентификация происходит на стороне Google. После того, как Вы идентифицировали пользователя, можете посылать ему информацию.
Приведенный ниже код демонстрирует проверку ID метки:
Java
import java.io.IOException; import java.security.GeneralSecurityException; import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken; import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier; import com.google.api.client.http.javanet.NetHttpTransport; import com.google.api.client.json.JsonFactory; import com.google.api.client.json.gson.GsonFactory; publicclass Checker{ private finalList mClientIDs; private final String mAudience; private final GoogleIdTokenVerifier mVerifier; private final JsonFactory mJFactory; private String mProblem="Verification failed. (Time-out?)"; public Checker(String[] clientIDs, String audience){ mClientIDs= Arrays.asList(clientIDs); mAudience= audience; NetHttpTransport transport=new NetHttpTransport(); mJFactory=new GsonFactory(); mVerifier=new GoogleIdTokenVerifier(transport, mJFactory); } public GoogleIdToken.Payload check(String tokenString){ GoogleIdToken.Payload payload=null; try{ GoogleIdToken token= GoogleIdToken.parse(mJFactory, tokenString); if(mVerifier.verify(token)){ GoogleIdToken.Payload tempPayload= token.getPayload(); if(!tempPayload.getAudience().equals(mAudience)) mProblem="Audience mismatch"; elseif(!mClientIDs.contains(tempPayload.getIssuee())) mProblem="Client ID mismatch"; else payload= tempPayload; } } catch(GeneralSecurityException e){ mProblem="Security issue: "+ e.getLocalizedMessage(); } catch(IOException e){ mProblem="Network problem: "+ e.getLocalizedMessage(); } return payload; } public String problem(){ return mProblem; } }
Ruby. Необходимо установить google-id-token Ruby gem
require'google-id-token' validator= GoogleIDToken::Validator.new jwt= validator.check(token, required_audience, required_client_id) if jwt email= jwt['email'] else report"Cannot validate: #{validator.problem}" end
PHP реализацию можно посмотретьздесь. В частности нужно обратить внимание на функцию verifyIdToken вapiOAuth2.php.
Оригинал:Tim Bray - "Verifying Back-End Calls from Android Apps"
Перевод:Александр Ледков