Статьи

Доступ к службам Google с использованием протокола OAuth 2.0

Это руководство будет посвящено внедрению Сервисов Google с использованием протокола OAuth 2.0. Читай дальше!


Протокол OAuth 2.0 обеспечивает простой и безопасный стандарт, который позволяет сторонним приложениям получать доступ к основным поставщикам услуг, таким как Facebook, G + и Twitter, без ущерба для паролей пользователей. Вся идея вращается вокруг существования токена доступа, что-то вроде уникального ключа, который может идентифицировать пользователя вместо пароля. Токены доступа получаются сторонними приложениями после успешной аутентификации пользователя с помощью веб-службы. Весь процесс, известный как поток авторизации, начинается, когда пользователь вводит свои учетные данные в окно входа в систему, и завершается, когда токен доступа получен. Маркер доступа обычно обновляется время от времени. Благодаря этому токену нет необходимости передавать какие-либо личные данные или пароль пользователя через Интернет каждый раз, когда клиентское приложение запрашивает доступ от имени пользователя.

Если вы не знакомы с протоколом OAuth 2.0, вам следует начать с фонового чтения, прежде чем продолжить этот урок. В частности, просмотрите следующие ссылки:

В общем случае поток авторизации OAuth 2.0 соответствует следующему шаблону:

  1. Разрешить пользователям подключаться к своей онлайн-учетной записи.
  2. Получить код авторизации (т.е. токен авторизации).
  3. Обменяйте код авторизации на токен доступа и токен обновления.
  4. Используйте токен доступа для взаимодействия с веб-сервисом или API.
  5. Используйте токен обновления для обновления токена доступа при необходимости.

Google является одним из многих сторонних поставщиков веб-услуг, которые приняли протокол OAuth 2.0. Он предоставляет множество API-интерфейсов для доступа почти ко всем своим службам (таким как Календарь, Blogger и т. Д.) Через клиентские приложения и предоставляет руководство по реализации процесса авторизации на различных языках программирования и платформах. Каждое приложение, которому необходимо использовать какую-либо из веб-служб Google, должно сначала зарегистрироваться в консоли разработчика Google, панели администрирования, где управляются все клиентские приложения, разработанные пользователем. При регистрации приложения в консоли специально для этого приложения создаются идентификатор клиента и секрет клиента . Эти значения, наряду с некоторыми другими, используются OAuth для авторизации приложения и получения токена доступа. После регистрации приложения появляется ряд доступных сервисов, которые можно интегрировать в проекты. Эти сервисы доступны через API, которые предоставляются для каждого из них. Некоторые из них бесплатны, в то время как другие требуют плату, прежде чем Google позволит вам использовать их сверх предела вежливости / пробного использования.

С этого момента, я предполагаю, что вы знакомы с принципами OAuth 2.0, поэтому пришло время посетить документацию по использованию OAuth 2.0 для установленных приложений от Google. В этой документации представлен процесс авторизации, поддерживаемый Google для клиентских приложений, реализованных для мобильных или настольных платформ, и он станет нашим руководством по проекту этого учебного пособия. Кроме того, я бы порекомендовал вам немного заняться серфингом, используя пункты меню в верхней левой части окна и ссылки внутри текста, чтобы глубже понять сервисы Google и то, как работает протокол OAuth 2.0.


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

Прежде чем мы начнем писать одну строку кода, нам нужно зарегистрировать демонстрационное приложение в консоли разработчиков Google. Для этого нам понадобится имя приложения. Ради нашего проекта я использую имя GoogleOAuthDemo . Если вы предпочитаете другое имя, обязательно используйте его вместо моего в следующих шагах.

На данный момент вот скриншот конечного продукта:

gt5_final

Нашим первым шагом является регистрация приложения iOS в консоли разработчика Google, поскольку нам необходимо получить идентификатор клиента и пару значений секретного клиента. Итак, в новой вкладке / окне браузера посетите сайт разработчиков Google . В верхней правой части окна есть кнопка «Войти». Нажмите на нее, и появится следующее окно.

gt5_2_signin_window

Если у вас есть учетная запись в каком-либо сервисе Google (например, gmail, Google+, Drive и т. Д.), Вы можете войти в систему, используя учетные данные из этого аккаунта. Если у вас его еще нет или вы просто хотите создать новый, сейчас самое время сделать это, просто нажав кнопку Зарегистрироваться.

gt5_3_signup_button

Если вы создаете новую учетную запись, введите необходимую информацию, создайте ее и войдите на сайт разработчиков Google.

После того, как вы подключитесь к учетной записи Google Developers, вам может стать весьма любопытно все, что содержится на этой странице, поэтому продолжайте и посмотрите, если хотите. Конечно, вы найдете несколько довольно интересных тем. Прокрутите до нижней части страницы, пока не увидите область Инструменты разработчика. Нажмите на ссылку API Console.

gt5_4_api_console_icon

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

Давайте вернемся на правильный путь. Когда вы впервые просматриваете эту веб-страницу, вы видите раскрывающееся меню в верхней левой части веб-страницы, которое называется API Project. Если вы развернете его, вы найдете несколько дополнительных опций, где с помощью них вы сможете создавать новые проекты, удалять их, переименовывать их и многое другое. Нам ничего от этого не нужно, поэтому, если вы развернули его, нажмите на него, чтобы свернуть. Под выпадающим меню есть четыре варианта:

  1. обзор
  2. Сервисы
  3. команда
  4. Доступ к API
gt5_5_dashboard

Если вы щелкнете по опции Сервисы, вы увидите все сервисы, которые Google предлагает с API. Рядом с каждой услугой есть некоторые примечания, указывающие, какая из них требует платы. Чтобы использовать любой из них в приложении, сначала необходимо зарегистрировать приложение, затем реализовать протокол OAuth и, наконец, запросить соответствующие API.

Нажмите на опцию доступа к API сейчас. Это место, где мы зарегистрируем наше новое приложение. Нажмите на большую синюю кнопку с заголовком «Создать идентификатор клиента OAuth 2.0 …» .

gt5_6_create_oauth_button

Во всплывающем окне добавьте в качестве имени продукта значение демонстрации реализации iOS в Google OAuth 2.0 и нажмите «Далее».

gt5_7_create_client_id_1

На следующем шаге установите следующее:

  • Тип приложения : Установленное приложение
  • Тип установленного приложения : iOS
  • Идентификатор пакета: com.yourdomain.googleoauthdemo (например, com.gabrieltheodoropoulos.googleoauthdemo)
gt5_8_create_client_id_2

Нажмите кнопку Создать идентификатор клиента сейчас, и вы готовы. Как вы можете видеть на следующем скриншоте, вы можете легко найти идентификатор клиента и секретные значения клиента.

gt5_9_client_id_secret

Вы можете сами убедиться, что можете редактировать всю информацию, которую мы только что ввели, используя предоставленные ссылки и кнопки на веб-странице. Данные, которые мы собираемся использовать в нашем демонстрационном приложении:

  • ID клиента
  • секрет клиента
  • URI перенаправления

На этом первая часть нашей миссии закончена. Наше приложение зарегистрировано в консоли разработчиков Google, и мы располагаем всей необходимой информацией. Однако, трудная часть идет дальше, где мы собираемся реализовать протокол OAuth 2.0 в коде.


Время начать строить наше приложение. Запустите Xcode и создайте новый проект. На первом шаге выберите опцию Single View Application из предоставленных шаблонов.

gt5_10_project_template

Затем укажите GoogleOAuthDemo в поле « Имя продукта » в окне параметров проекта.

gt5_11_project_options

Наконец, выберите место для сохранения вашего проекта и нажмите кнопку «Создать».

gt5_12_project_create

Нажмите на файл ViewController.xib чтобы запустить Interface Builder. Перетащите следующие подпредставления в представление по умолчанию:

  1. Табличное представление .
  2. Панель инструментов .

Кроме того, помимо элемента панели кнопок по умолчанию, который содержит панель инструментов, добавьте:

  1. Гибкая клавиша пробела и пункт
  2. Панель кнопок

Убедитесь, что одна кнопка находится на левой стороне панели инструментов, а вторая — на правой. Гибкое пространство должно быть между ними.

Давайте теперь сделаем пару настроек.

  • Выберите « Вид» и установите для его размера значение « Нет», чтобы оно работало и на экранах iPhone 3,5 дюйма
    1. Откройте панель « Утилиты» .
    2. Показать вкладку « Инспектор атрибутов ».
    3. В разделе Simulated Metrics установите для размера « Нет» значение .
    gt5_13_size_none
  • Установите рамку вида таблицы: X: 0, Y: 0, ширина: 320, высота: 416 .
  • Установите заголовок элемента левой кнопки панели в « Мой профиль» .
  • Установите заголовок элемента правой кнопки панели, чтобы отменить доступ .

Это все, что мы хотим от нашего интерфейса, и вот как это должно выглядеть сейчас:

gt5_14_interface_sample

Наш интерфейс подготовлен, но нам нужно свойство IBOutlet для соединения табличного представления. Нам также нужны два метода IBAction, если мы хотим, чтобы наши кнопки работали. Итак, пока мы находимся в Интерфейсном Разработчике, нажмите среднюю кнопку раздела «Редактор» на панели инструментов Xcode, чтобы появился помощник редактора .

gt5_15_assistant_editor

Чтобы создать необходимое соединение IBOutlet, откройте схему документа и выполните следующие действия:

  1. Ctrl + клик или правая + клик по представлению таблицы.
  2. Во всплывающем меню выберите «Новый источник ссылок».
  3. Перетащите в окно Assistant Editor, как вы видите на следующем изображении.
gt5_16_iboutlet_create

Дайте имя Табличному Представлению и нажмите на кнопку Подключиться . Я просто назвал это таблицей .

gt5_17_iboutlet_name

Давайте создадим два метода IBAction сейчас. Примените следующую процедуру в обоих пунктах панели кнопок.

  1. Контрол + клик или правый клик на левом элементе панели кнопок.
  2. Нажмите на опцию «Выбор» в разделе «Отправленные действия» всплывающего меню.
  3. Перетащите в окно помощника редактора.
gt5_18_ibaction_create

Установите showProfile как имя метода и создайте его.

gt5_19_ibaction_name

Присвойте имя revokeAccess следующему методу IBAction при его создании. Ваш файл ViewController.h после свойства IBOutlet и методов IBAction должен выглядеть следующим образом:

1
2
3
4
5
6
7
8
9
#import <UIKit/UIKit.h>
  
@interface ViewController : UIViewController
@property (weak, nonatomic) IBOutlet UITableView *table;
  
— (IBAction)showProfile:(id)sender;
— (IBAction)revokeAccess:(id)sender;
  
@end

Несмотря на то, что мы добавили табличное представление в проект, мы пока не будем реализовывать его методы делегирования. Это то, что мы собираемся сделать, когда реализация OAuth 2.0 закончится. На этом этапе мы собираемся создать новый файл, в котором мы будем строить протокол OAuth.

В Навигаторе проекта сделайте следующее:

  1. Control + Click или Right + Click в группе GoogleOAuthDemo .
  2. Выберите опцию Новый файл … из меню.
gt5_20_new_file

В появившемся окне выберите параметр класса Objective C в качестве шаблона для нового файла:

gt5_21_new_file_template

Затем в поле Подкласс поля выберите опцию UIWebView . Наш новый класс станет подклассом класса UIWebView , потому что мы хотим показать пользователям веб-представление, позволяющее им войти в систему с помощью своей учетной записи Google и предоставить им доступ к нашему приложению. В поле Class добавьте значение GoogleOAuth :

gt5_22_new_file_options

На следующем шаге просто нажмите кнопку «Создать», чтобы создать новые файлы.

gt5_23_new_file_create

В этой части я кратко опишу, как будет реализован поток OAuth 2.0 и как будет проходить вся необходимая проверка на наличие токенов и действительность токена доступа. Прежде всего, после того, как токен доступа был получен, он должен постоянно храниться вместе с любой другой информацией, которая идет с ним. Разработчик должен выбрать способ сохранения всей этой информации. Вы можете сохранить все в базе данных, сохранить его по умолчанию для пользователя, записать все в файлы или использовать более безопасные методы для обеспечения безопасности. В этом уроке я предпочитаю сохранять все в виде простых файлов в каталоге Documents приложения.

Мы собираемся создать два разных файла. Токен доступа и любая другая информация, которая приходит с ним от Google, будет сохранена в первом, кроме токена обновления. Токен обновления будет сохранен в другом файле, потому что нам нужно хранить его в течение более длительного периода времени, чем все остальное, и использовать его после истечения срока действия токена доступа. Каждый раз, когда токен доступа обновляется, содержимое первого файла перезаписывается. Кроме того, сам токен обновления обновляется не так часто.

Итак, сказав все это, вот предварительный просмотр потока протокола и того, как он будет реализован.

gt5_oauth_flow

Даже если изображение равно тысяче слов, я бы лучше описал всю процедуру, чтобы сделать ее более понятной. Чтобы уточнить процесс авторизации:

  • Если файл с информацией о токене доступа не существует …
  1. URL-адрес, от которого нам потребуется токен доступа, формируется. Все необходимые параметры для аутентификации пользователя предоставляются. Идентификатор клиента и секретные значения клиента необходимы здесь.
  2. Затем мы отображаем веб-представление внутри приложения (мы не называем Safari), чтобы позволить пользователю войти в Google. После успешного входа в систему пользователю Google показывается сообщение с разрешениями, которые мы запрашиваем, и он должен дать согласие на дальнейшие действия.
  3. Код авторизации возвращается от Google, который сразу же будет заменен токеном доступа.
  4. Новый URL формируется с новыми параметрами. Целью является обмен кода авторизации с токеном доступа.
  5. Если маркер доступа успешно получен, класс вызывающей стороны сообщается с помощью метода делегата. В противном случае, используя другой метод делегата, мы сообщаем классу вызывающей стороны, что токен доступа не был получен, поэтому при необходимости необходимо предпринять дальнейшие действия.
  • Если файл с информацией о токене доступа уже существует …
    1. Информация о токене доступа загружается из файла.
    2. Токен доступа проверяется, чтобы увидеть, истек ли он или нет.
    3. Если токен доступа действителен, то класс вызывающего просто получает уведомление через допустимый метод делегата, и эти шаги на этом заканчиваются. В противном случае требуется обновление (следующие шаги).
    4. Если файл, содержащий токен обновления, не найден в каталоге Documents, тогда весь поток OAuth начинается заново. Если он существует, загружается токен обновления.
    5. Запрошен новый токен доступа. Если токен обновления недействителен, Google вернет сообщение об ошибке и пользователь снова перейдет к началу. В противном случае токен доступа получен.
    6. Итак, давайте все рассмотрим шаг за шагом.


      Сначала откройте файл GoogleOAuth.h и GoogleOAuth.h следующие два протокола, просто объявив их в строке заголовка @interface :

      1
      2
      3
      @interface OAuth : UIWebView <UIWebViewDelegate, NSURLConnectionDataDelegate>
       
      @end

      Я совершенно уверен, что совершенно очевидно, для чего эти делегаты.

      Теперь давайте создадим тип enum с приемлемыми значениями метода HTTP, необходимыми для различных вызовов API. В настоящее время мы создадим тип enum, но мы будем использовать его при реализации метода вызова API.

      Итак, прямо над строкой GoogleOAuth.h файле GoogleOAuth.h добавьте следующее:

      1
      2
      3
      4
      5
      6
      typedef enum {
          httpMethod_GET,
          httpMethod_POST,
          httpMethod_DELETE,
          httpMethod_PUT
      } HTTP_Method;

      Всегда полезно использовать константы для вещей, в которых мы не хотим допускать ошибок, и это верно для конечных точек URL, к которым мы хотим получить доступ во время потока OAuth. По этой причине откройте файл GoogleOAuth.m и объявите следующие две константы в верхней части файла:

      1
      2
      #define authorizationTokenEndpoint @»https://accounts.google.com/o/oauth2/auth»
      #define accessTokenEndpoint @»https://accounts.google.com/o/oauth2/token»

      Первый URL-адрес будет использоваться для доступа к конечной точке для получения кода авторизации, который мы затем обменяем с токеном доступа, используя вторую конечную точку. Давайте двигаться дальше и объявим некоторые частные свойства, которые будут содержать некоторые важные данные.

      При работе в файле GoogleOAuth.m внутри приватной части интерфейса добавьте следующие свойства. Над каждым свойством есть комментарии, объясняющие их назначение:

      01
      02
      03
      04
      05
      06
      07
      08
      09
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      // The client ID from the Google Developers Console.
      @property (nonatomic, strong) NSString *clientID;
      // The client secret value from the Google Developers Console.
      @property (nonatomic, strong) NSString *clientSecret;
      // The redirect URI after the authorization code gets fetched.
      @property (nonatomic, strong) NSString *redirectUri;
      // The authorization code that will be exchanged with the access token.
      @property (nonatomic, strong) NSString *authorizationCode;
      // The refresh token.
      @property (nonatomic, strong) NSString *refreshToken;
      // An array for storing all the scopes we want authorization for.
      @property (nonatomic, strong) NSMutableArray *scopes;
       
      // A NSURLConnection object.
      @property (nonatomic, strong) NSURLConnection *urlConnection;
      // The mutable data object that is used for storing incoming data in each connection.
      @property (nonatomic, strong) NSMutableData *receivedData;
       
      // The file name of the access token information.
      @property (nonatomic, strong) NSString *accessTokenInfoFile;
      // The file name of the refresh token.
      @property (nonatomic, strong) NSString *refreshTokenFile;
      // A dictionary for keeping all the access token information together.
      @property (nonatomic, strong) NSMutableDictionary *accessTokenInfoDictionary;
       
      // A flag indicating whether an access token refresh is on the way or not.
      @property (nonatomic) BOOL isRefreshing;
       
      // The parent view where the webview will be shown on.
      @property (nonatomic, strong) UIView *parentView;

      Обратите внимание, что для токена доступа нет определенного свойства NSString, но у нас есть accessTokenInfoDictionary NSMutableDictionary для хранения в нем всех данных, связанных с токеном доступа.

      Время сделать некоторые инициализации. Перейдите во - (id)initWithFrame:(CGRect)frame и добавьте следующий код:

      01
      02
      03
      04
      05
      06
      07
      08
      09
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      — (id)initWithFrame:(CGRect)frame
      {
          self = [super initWithFrame:frame];
          if (self) {
              // Set the access token and the refresh token file paths.
              NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
              NSString *docDirectory = [paths objectAtIndex:0];
              _accessTokenInfoFile = [[NSString alloc] initWithFormat:@»%@/acctok», docDirectory];
              _refreshTokenFile = [[NSString alloc] initWithFormat:@»%@/reftok», docDirectory];
           
              // Set the redirect URI.
              // This is taken from the Google Developers Console.
              _redirectUri = @»urn:ietf:wg:oauth:2.0:oob»;
               
              // Make any other required initializations.
              _receivedData = [[NSMutableData alloc] init];
              _urlConnection = [[NSURLConnection alloc] init];
              _refreshToken = nil;
              _isRefreshing = NO;
          }
          return self;
      }

      Остальные инициализации будут происходить внутри методов, которые они будут использовать, так что пока у нас все хорошо.


      Определение протокола внутри нового класса — это задача, которая обычно выполняется после того, как большая часть функциональности класса была завершена, но в нашем случае было бы хорошо, если бы мы подготовили определение протокола, прежде чем идти дальше. На самом деле мы будем объявлять методы протокола, которые будут реализованы позже классами, которые примут протокол. Итак, внутри файла GoogleOAuth.h и над строкой GoogleOAuth.h добавьте следующие несколько строк:

      1
      2
      3
      4
      5
      6
      7
      @protocol GoogleOAuthDelegate
      -(void)authorizationWasSuccessful;
      -(void)accessTokenWasRevoked;
      -(void)responseFromServiceWasReceived:(NSString *)responseJSONAsString andResponseJSONAsData:(NSData *)responseJSONAsData;
      -(void)errorOccuredWithShortDescription:(NSString *)errorShortDescription andErrorDetails:(NSString *)errorDetails;
      -(void)errorInResponseWithBody:(NSString *)errorMessage;
      @end

      Вот краткое описание вышеуказанных методов:

    • authorizationWasSuccessful : он будет использоваться после успешной авторизации, то есть после получения действительного токена доступа.
    • accessTokenWasRevoked : этот метод делегата будет использоваться, когда пользователь аннулирует все предоставленные разрешения.
    • responseFromServiceWasReceived:andResponseJSONAsData: этот метод будет вызываться при каждом получении ответа на вызов API.
    • errorOccuredWithShortDescription:andErrorDetails: при возникновении общей ошибки.
    • errorInResponseWithBody: этот метод делегата будет вызван при наличии ошибки в ответе HTTP.

    Затем, внутри @interface , добавьте следующую строку:

    1
    2
    3
    4
    @interface OAuth : UIWebView <UIWebViewDelegate, NSURLConnectionDataDelegate>
    @property (nonatomic, strong) id<GoogleOAuthDelegate> gOAuthDelegate;
     
    @end

    До сих пор мы объявляли все закрытые переменные-члены, которые нам понадобятся, и инициализировали некоторые из них, мы определяли константы, делегаты и протокол, поэтому пришло время приступить к реализации фактического потока авторизации. Лучше всего начать с «точки входа», открытого метода, который пользователь будет вызывать каждый раз, когда необходимо авторизоваться или использовать API.

    Перейдите в файл GoogleOAuth.h и добавьте следующее объявление метода:

    1
    2
    3
    4
    5
    @interface GoogleOAuth : UIWebView <UIWebViewDelegate, NSURLConnectionDataDelegate>
    @property (nonatomic, strong) id<GoogleOAuthDelegate> gOAuthDelegate;
    -(void)authorizeUserWithClienID:(NSString *)client_ID andClientSecret:(NSString *)client_Secret
                        andParentView:(UIView *)parent_View andScopes:(NSArray *)scopes;
    @end

    Теперь перейдем к реализации. Я думаю, что было бы лучше сначала создать метод, а потом немного его обсудить. Внутри файла GoogleOAuth.m добавьте следующий фрагмент:

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    -(void)authorizeUserWithClienID:(NSString *)client_ID
                          andClientSecret:(NSString *)client_Secret
                          andParentView:(UIView *)parent_View
                          andScopes:(NSArray *)scopes{
         
        // Store into the local private properties all the parameter values.
        _clientID = [[NSString alloc] initWithString:client_ID];
        _clientSecret = [[NSString alloc] initWithString:client_Secret];
        _scopes = [[NSMutableArray alloc] initWithArray:scopes copyItems:YES];
        _parentView = parent_View;
         
        // Check if the access token info file exists or not.
        if ([self checkIfAccessTokenInfoFileExists]) {
            // In case it exists load the access token info and check if the access token is valid.
            [self loadAccessTokenInfo];
            if ([self checkIfShouldRefreshAccessToken]) {
                // If the access token is not valid then refresh it.
                [self refreshAccessToken];
            }
            else{
                // Otherwise tell the caller through the delegate class that the authorization is successful.
                [self.gOAuthDelegate authorizationWasSuccessful];
            }
             
        }
        else{
            // In case that the access token info file is not found then show the
            // webview to let user sign in and allow access to the app.
            [self showWebviewForUserLogin];
        }
    }

    Давайте немного поговорим о том, как работает метод, который мы только что написали. Как видите, первое, что мы делаем, — это сохраняем все значения, передаваемые в метод, в качестве параметров локальным свойствам. Это идентификатор клиента, секрет клиента, массив областей и UIView , внутри которого должно появиться UIView представление входа в систему.

    Область действия указывает на API, к которому приложение запрашивает доступ.

    Затем мы проверяем, существует ли файл информации о токене доступа или нет. Если он существует, мы загружаем его с loadAccessTokenInfo метода loadAccessTokenInfo . Мы проверяем, является ли он действительным, с checkIfShouldRefreshAccessToken метода checkIfShouldRefreshAccessToken а если нет, мы обновляем его с помощью метода refreshAccessToken . Если токен доступа действителен и срок его действия еще не истек, мы просто сообщаем классу вызывающего абонента с помощью метода делегирования authorizationWasSuccessful . Наконец, если информационный файл маркера доступа не существует, мы просто вызываем метод showWebviewForUserLogin чтобы заставить веб-представление появиться и позволить пользователю войти в систему.

    Все вышеперечисленные методы будут реализованы один за другим.


    Как вы можете видеть в приведенном выше фрагменте, метод почти полностью работает с использованием других закрытых методов. Прежде чем перейти к следующим шагам, рекомендуется объявить все закрытые методы, которые мы собираемся реализовать, в разделе private @interface .

    Чтобы облегчить понимание всей идеи, я предпочитаю разделять частные методы на две категории:

    1. Первый включает те методы, которые являются частью самого потока авторизации. Эти:
      1
      2
      3
      4
      5
      6
      7
      8
      @interface OAuth()
      -(void)showWebviewForUserLogin;
      -(void)exchangeAuthorizationCodeForAccessToken;
      -(void)refreshAccessToken;
       
      @end
    2. Вторая категория включает в себя вспомогательные методы, роль которых заключается в поддержке потока авторизации путем реализации других необходимых операций для правильной работы всей процедуры. Это включает:
      01
      02
      03
      04
      05
      06
      07
      08
      09
      10
      11
      12
      13
      @interface OAuth()
      -(NSString *)urlEncodeString:(NSString *)stringToURLEncode;
      -(void)storeAccessTokenInfo;
      -(void)loadAccessTokenInfo;
      -(void)loadRefreshToken;
      -(BOOL)checkIfAccessTokenInfoFileExists;
      -(BOOL)checkIfRefreshTokenFileExists;
      -(BOOL)checkIfShouldRefreshAccessToken;
      -(void)makeRequest:(NSMutableURLRequest *)request;
       
      @end

    Помимо всего вышеперечисленного, мы реализуем следующий метод делегата для веб-просмотра:

    1
    -(void)webViewDidFinishLoad:(UIWebView *)webView;

    Используя его, мы будем «читать» код авторизации.

    Кроме того, мы реализуем следующие NSURLConnection делегата NSURLConnection :

    1
    2
    3
    4
    -(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;
    -(void)connectionDidFinishLoading:(NSURLConnection *)connection;
    -(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;
    -(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;

    Давайте двигаться дальше и посмотрим, как все они связаны друг с другом.


    Давайте продолжим, написав метод -(void)showWebviewForUserLogin . Этот метод служит двум целям. Первый — сформировать URL (со всеми необходимыми параметрами), который наше приложение будет использовать для получения кода авторизации. Второй — показать пользователям веб-просмотр, с помощью которого они войдут в свою учетную запись Google и предоставят права доступа к приложению. Давайте сначала посмотрим на метод.

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    -(void)showWebviewForUserLogin{
        // Create a string to concatenate all scopes existing in the _scopes array.
        NSString *scope = @»»;
        for (int i=0; i<[_scopes count]; i++) {
            scope = [scope stringByAppendingString:[self urlEncodeString:[_scopes objectAtIndex:i]]];
             
            // If the current scope is other than the last one, then add the «+» sign to the string to separate the scopes.
            if (i < [_scopes count] — 1) {
                scope = [scope stringByAppendingString:@»+»];
            }
        }
         
        // Form the URL string.
        NSString *targetURLString = [NSString stringWithFormat:@»%@?scope=%@&amp;redirect_uri=%@&amp;client_id=%@&amp;response_type=code»,
                                     authorizationTokenEndpoint,
                                     scope,
                                     _redirectUri,
                                     _clientID];
         
         
        // Do some basic webview setup.
        [self setDelegate:self];
        [self setScalesPageToFit:YES];
        [self setAutoresizingMask:_parentView.autoresizingMask];
         
        // Make the request and add self (webview) to the parent view.
        [self loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:targetURLString]]];
        [_parentView addSubview:self];
    }

    Пара наблюдений сейчас. Сначала все области, существующие в массиве _scopes , объединяются в одну NSString после кодирования URL . Это необходимо, поскольку каждый специальный символ, который может существовать в области видимости, должен быть заменен эквивалентным символом в кодировке URL (подробнее об этом позже). Временная строка используется в целях конкатенации. Внутри объединенной строки области видимости разделяются знаком «+».

    Далее формируется строка URL. Обратите особое внимание на параметры, которые он принимает.

    Мы предоставляем:

    • Конечная точка кода авторизации
    • Области применения
    • URI перенаправления, который является постоянным значением для установленных приложений
    • Идентификатор клиента, который мы получили из консоли разработчиков Google
    • Значение response_type которое установлено в стандартное значение code

    Во второй половине метода мы просто настраиваем веб-представление, которое будет отображаться пользователю. Здесь нет ничего конкретного, на что я должен указывать, код достаточно прост. Просто отметьте, что после настройки всего и вызова метода loadRequest, веб-представление добавляется как дочернее представление к родительскому представлению.

    После того как пользователь вошел в систему и согласился предоставить доступ к нашему приложению, код авторизации возвращается из Google. На самом деле, есть два способа получить это. Первый включает использование файлов cookie , где код авторизации хранится в файле cookie. Google предоставляет пример того, как использовать этот способ. Второй способ, который я предпочитаю использовать, — это получить код авторизации из заголовка страницы веб-представления. Как только мы его получим, мы готовы приступить к получению токена доступа.

    Это то, что мы собираемся сделать в единственном методе делегата веб-просмотра, который мы собираемся реализовать. Давайте посмотрим это в действии.

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    -(void)webViewDidFinishLoad:(UIWebView *)webView{
        // Get the webpage title.
        NSString *webviewTitle = [webView stringByEvaluatingJavaScriptFromString:@»document.title»];
        //NSLog(@»Webview Title = %@», webviewTitle);
         
        // Check for the «Success token» literal in title.
        if ([webviewTitle rangeOfString:@»Success code»].location != NSNotFound) {
            // The oauth code has been retrieved.
            // Break the title based on the equal sign (=).
            NSArray *titleParts = [webviewTitle componentsSeparatedByString:@»=»];
            // The second part is the oauth token.
            _authorizationCode = [[NSString alloc] initWithString:[titleParts objectAtIndex:1]];
             
            // Show a «Please wait…» message to the webview.
            NSString *html = @»<html><head><title>Please wait</title></head><body><h1>Please wait…</h1></body></html>»;
            [self loadHTMLString:html baseURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]]];
             
            // Exchange the authorization code for an access code.
            [self exchangeAuthorizationCodeForAccessToken];
        }
        else{
            if ([webviewTitle rangeOfString:@»access_denied»].location != NSNotFound) {
                // In case that the user tapped on the Cancel button instead of the Accept, then just
                // remove the webview from the superview.
                [webView removeFromSuperview];
            }
        }
    }

    Код авторизации, который возвращается, имеет вид, аналогичный code=4/a5F4r45... Поэтому в приведенном выше фрагменте заголовок разбивается на части, основанные на знаке равенства «=». После этого мы готовы обменять его на токен доступа.

    Теперь, когда токен авторизации получен, веб-просмотр больше не нужен. Мы удалим его, хотя в конце всего процесса (следующий шаг). NSURLConnection и NSURLRequest будут использоваться с этого NSURLRequest . В методе exchangeAuthorizationCodeForAccessToken мы должны просто настроить все необходимые параметры, необходимые для получения токена доступа, и сделать запрос к конечной точке токена доступа. Самое важное, на что я должен обратить внимание, это то, что используется HTTP-метод POST . Вот метод.

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    -(void)exchangeAuthorizationCodeForAccessToken{
        // Create a string containing all the post parameters required to exchange the authorization code
        // with the access token.
        NSString *postParams = [NSString stringWithFormat:@»code=%@&amp;client_id=%@&amp;client_secret=%@&amp;redirect_uri=%@&amp;grant_type=authorization_code»,
                                _authorizationCode,
                                _clientID,
                                _clientSecret,
                                _redirectUri];
         
        // Create a mutable request object and set its properties.
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:accessTokenEndpoint]];
        [request setHTTPMethod:@»POST»];
        [request setHTTPBody:[postParams dataUsingEncoding:NSUTF8StringEncoding]];
        [request setValue:@»application/x-www-form-urlencoded» forHTTPHeaderField:@»Content-Type»];
         
        // Make the request.
        [self makeRequest:request];
    }

    Обратите внимание на использование кода авторизации, который мы получили на предыдущем шаге.

    При выполнении запроса и использовании NSURLConnection могут произойти две вещи. Либо соединение будет успешно завершено, либо произойдет сбой. В нашем случае, когда запрос не выполняется, мы хотим сообщить классу вызывающей стороны только через делегата, и это все. Итак, давайте напишем первый NSURLConnection делегата NSURLConnection .

    1
    2
    3
    -(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
        [self.gOAuthDelegate errorOccuredWithShortDescription:@»Connection failed.»
    }

    Вы видите, как наши методы делегата становятся удобными. Теперь перейдем к случаю, когда соединение успешно завершено и токен доступа получен.

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    -(void)connectionDidFinishLoading:(NSURLConnection *)connection{
        // This object will be used to store the converted received JSON data to string.
        NSString *responseJSON;
         
        // This flag indicates whether the response was received after an API call and out of the
        // following cases.
        BOOL isAPIResponse = YES;
         
        // Convert the received data in NSString format.
        responseJSON = [[NSString alloc] initWithData:(NSData *)_receivedData encoding:NSUTF8StringEncoding];
            
        // Check for access token.
        if ([responseJSON rangeOfString:@»access_token»].location != NSNotFound) {
            // This is the case where the access token has been fetched.
            [self storeAccessTokenInfo];
             
            // Remove the webview from the superview.
            [self removeFromSuperview];
             
            if (_isRefreshing) {
                _isRefreshing = NO;
            }
             
            // Notify the caller class that the authorization was successful.
            [self.gOAuthDelegate authorizationWasSuccessful];
             
            isAPIResponse = NO;
        }
    }

    Не обманывайте себя, полагая, что это будет единственным содержанием этого метода делегата. На самом деле, он станет каким-то большим, но сейчас вы не должны обращать на это внимание.

    Примечание. Видите ли вы флаг isAPIResponse ? Ну, это полезно знать, когда ответ касается API или самого процесса авторизации. Когда речь идет о любом случае, связанном с процессом авторизации, он становится НЕТ, как вы можете видеть в случае маркера доступа. Вы полностью поймете его значение на этом пути.

    Google возвращает объект JSON, который содержит токен доступа вместе с другими данными. Первое, что мы делаем, — это конвертируем полученные данные в строковое значение, чтобы мы могли легко различить, какие данные были получены, и правильно обработать их. Делая это в начале метода, мы проверяем, действительно ли был получен токен доступа, просто ища его в строке ответа. Если это там, то … вау, мы только что получили токен доступа! Давайте позаботимся об этом сейчас. Сначала мы сохраняем его в файл и удаляем веб-представление из суперпредставления. В случае, если токен доступа был получен в процессе обновления, мы устанавливаем соответствующий флаг на NO . Наконец, мы используем наш делегат, чтобы сообщить классу вызывающей стороны, что токен доступа был успешно получен.

    Возвращаемый файл JSON выглядит примерно так:

    1
    2
    3
    4
    5
    6
    7
    {
      «access_token» : «ya29.AHES6ZSpPmQZwz30EcyhWE9mvi_lgvh4DX8gSQ9GpIbkLL5qhP9sYQ»,
      «token_type» : «Bearer»,
      «expires_in» : 3600,
      «id_token» : «eyJhbGciOiJSUzI1NiIsImtpZCI6ImUxYzdhM2NkZGFmMzcxOWFlMGNlZGUzOTI4ZmZlZDI1MGFmMmQyODMifQ.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwidG9rZW5faGFzaCI6ImNfWngxMzBaWGQySFlCT0lEWlhiVUEiLCJhdF9oYXNoIjoiY19aeDEzMFpYZDJIWUJPSURaWGJVQSIsImNpZCI6Ijg4ODk5MTE4OTQ4OS5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImF6cCI6Ijg4ODk5MTE4OTQ4OS5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImF1ZCI6Ijg4ODk5MTE4OTQ4OS5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImlkIjoiMTE0MzE1NTUzNzc0OTY1NTIwNTIzIiwic3ViIjoiMTE0MzE1NTUzNzc0OTY1NTIwNTIzIiwiaWF0IjoxMzczMjE5NDYwLCJleHAiOjEzNzMyMjMzNjB9.jM8_m8aqwW56V2YHob5d-Xlb69yTRBrd_FgagJlSSOxEgRcxpn3D6uDDBmbkiI7S_UgvCc07CAis9wmzTGrKvjpp8JR04ka_LlzdvlaWlFlMvCZgs13GNP8fpi_o4jTgpMLeUM47ZZbBtF0C2uU8XCaVAzTW-VOYZkNPT2SjDY4»,
      «refresh_token» : «1/xZWny-TMV0jZvDRuHxwMl5tTZSiN8yCGP7gaILbPPxk»
    }

    На этом этапе добавьте следующий сегмент кода над if ([_responseJSON rangeOfString:@"access_token"].location != NSNotFound) { строка. Это необходимо для обработки любого недействительного запроса, который может возникнуть. Если вы удивляетесь, почему это может произойти, просто попробуйте поиграть с полным процессом авторизации или обновления, и вы легко узнаете.

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    if ([responseJSON rangeOfString:@»invalid_request»].location != NSNotFound) {
        NSLog(@»General error occured.»);
         
        // If a refresh was on the way then set the respective flag to NO.
        if (_isRefreshing) {
            _isRefreshing = NO;
        }
         
        // Notify the caller class through the delegate.
        [self.gOAuthDelegate errorInResponseWithBody:responseJSON];
         
         
        isAPIResponse = NO;
    }

    Весьма важным является следующий NSURLConnection делегата NSURLConnection , если вы хотите, чтобы вышеупомянутые два работали правильно.

    1
    2
    3
    4
    -(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
        // Append any new data to the _receivedData object.
        [_receivedData appendData:data];
    }

    Процесс авторизации завершен. Однако предстоит еще много работы, прежде чем мы будем готовы сказать, что у нас полностью реализованный класс.


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

    Чтобы проверить, является ли токен доступа действительным или нет, мы будем использовать метод checkIfShouldRefreshAccessToken . Мы реализуем это чуть позже, вместе с остальными вспомогательными приватными методами. А сейчас давайте сосредоточимся на процессе обновления.

    То, как мы обновляем токен доступа, очень похоже на то, как мы обмениваем код авторизации для токена доступа. Мы устанавливаем параметры POST и выполняем NSURLRequest чтобы получить новый токен доступа. Вот метод:

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    -(void)refreshAccessToken{
        // Load the refrest token if it’s not loaded alredy.
        if (_refreshToken == nil) {
            [self loadRefreshToken];
        }
         
        // Set the HTTP POST parameters required for refreshing the access token.
        NSString *refreshPostParams = [NSString stringWithFormat:@»refresh_token=%@&client_id=%@&client_secret=%@&grant_type=refresh_token»,
                                       _refreshToken,
                                       _clientID,
                                       _clientSecret
                                       ];
         
        // Indicate that an access token refresh process is on the way.
        _isRefreshing = YES;
         
        // Create the request object and set its properties.
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:accessTokenEndpoint]];
        [request setHTTPMethod:@»POST»];
        [request setHTTPBody:[refreshPostParams dataUsingEncoding:NSUTF8StringEncoding]];
        [request setValue:@»application/x-www-form-urlencoded» forHTTPHeaderField:@»Content-Type»];
         
        // Make the request.
        [self makeRequest:request];
    }

    Как видите, нам нужен только токен обновления, полученный с токеном доступа, идентификатором клиента, секретом клиента и стандартным значением grant_type . Здесь нет ничего сложного или нового.

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

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

    Внутри -(void)connectionDidFinishLoading:(NSURLConnection *)connectionметода делегата случай получения токена доступа из ответа уже существует. Мы также должны добавить новый сегмент кода, который будет проверять, ответил ли Google, что у нас неверный токен обновления. Итак, прямо перед if ([_responseJSON rangeOfString:@"access_token"].location != NSNotFound)строкой добавьте следующее:

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    // Check for invalid refresh token.
    // In that case guide the user to enter the credentials again.
    if ([responseJSON rangeOfString:@"invalid_grant"].location != NSNotFound) {
        if (_isRefreshing) {
            _isRefreshing = NO;
        }
         
        [self showWebviewForUserLogin];
         
        isAPIResponse = NO;
    }

    Вызывая showWebviewForUserLoginметод, мы начинаем всю процедуру заново.


    Пришло время реализовать остальные вспомогательные, частные методы, которые мы уже объявили.

    Давайте начнем с -(NSString *)urlEncodeString:(NSString *)stringToURLEncodeметода, который просто используется для преобразования строк, связанных с URL, в форму в кодировке URL. При отправке данных через Интернет не каждый символ может существовать в URL. Он должен быть заменен другими специальными символами, которые совместимы с URL, и это именно то, что делает следующий метод:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    -(NSString *)urlEncodeString:(NSString *)stringToURLEncode{
        // URL-encode the parameter string and return it.
        CFStringRef encodedURL = CFURLCreateStringByAddingPercentEscapes(NULL,
                                                                         (CFStringRef) stringToURLEncode,
                                                                         NULL,
                                                                         (CFStringRef)@"!@#$%&*'();:=+,/?[]",
                                                                         kCFStringEncodingUTF8);
        return (NSString *)CFBridgingRelease(encodedURL);
    }

    Как вы можете видеть, CFURLCreateStringByAddingPercentEscapesфункция, о которой я бы посоветовал прочитать больше , возвращает CFStringRefобъект типа, который затем преобразуется в момент NSStringего возврата. Нетрудно понять параметры, которые принимает эта функция. Обратите внимание, что мы предоставляем «список» символов, который следует заменить символами процента. Например ,! преобразуется в % 21 , * в % 2A и т. д. Последний параметр относится к желаемой кодировке.

    Мы продолжаем со следующим:

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    -(void)storeAccessTokenInfo{
        NSError *error;
         
        // Keep the access token info into a dictionary.
        _accessTokenInfoDictionary = [NSJSONSerialization JSONObjectWithData:_receivedData options:NSJSONReadingMutableContainers error:&error];
         
        // Check if any error occured while converting NSData data to NSDictionary.
        if (error) {
            [self.gOAuthDelegate errorOccuredWithShortDescription:@"An error occured while saving access token info into a NSDictionary."
                                                    andErrorDetails:[error localizedDescription]];
        }
         
        // Save the dictionary to a file.
        [_accessTokenInfoDictionary writeToFile:_accessTokenInfoFile atomically:YES];
     
         
        // If a refresh token is found inside the access token info dictionary then save it separately.
        if ([_accessTokenInfoDictionary objectForKey:@"refresh_token"] != nil) {
            // Extract the refresh token.
            _refreshToken = [[NSString alloc] initWithString:[_accessTokenInfoDictionary objectForKey:@"refresh_token"]];
             
            // Save the refresh token as data.
            [_refreshToken writeToFile:_refreshTokenFile atomically:YES encoding:NSUTF8StringEncoding error:&error];
             
            // If an error occurs while saving the refresh token notify the caller class.
            if (error) {
                [self.gOAuthDelegate errorOccuredWithShortDescription:@"An error occured while saving the refresh token."
                                                      andErrorDetails:[error localizedDescription]];
            }
        }
    }

    Здесь все просто. Сначала полученные данные, содержащие информацию о токене доступа, сохраняются в accessTokenInfoDictionaryнепостоянном словаре, который, в свою очередь, записывается в файл. Затем в возвращенной информации проверяется наличие маркера обновления. Если он найден, он извлекается и сохраняется в другой файл. Помните, что мы должны хранить токен обновления отдельно от файла токена доступа.

    Давайте посмотрим на loadAccessTokenInfoметод сейчас и поговорим об этом:

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    -(void)loadAccessTokenInfo{
        // Check if the access token info file exists.
        if ([self checkIfAccessTokenInfoFileExists]) {
            // Load the access token info from the file into the dictionary.
            _accessTokenInfoDictionary = [[NSMutableDictionary alloc] initWithContentsOfFile:_accessTokenInfoFile];
        }
        else{
            // If the access token info file doesn't exist then inform the caller class through the delegate.
            [self.gOAuthDelegate errorOccuredWithShortDescription:@"Access token info file was not found."
                                                  andErrorDetails:@""];
        }
    }

    Прежде всего, мы проверяем, существует ли файл информации о токене доступа или нет. Если он существует, он загружается в accessTokenInfoDictionary. Если файл по какой-либо причине не найден, errorOccuredWithShortDescription:andErrorDetails:вызывается метод делегата для информирования класса вызывающей стороны.

    Теперь давайте посмотрим, как загрузить токен обновления:

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    -(void)loadRefreshToken{
        // Check if the refresh token file exists.
        if ([self checkIfRefreshTokenFileExists]) {
            NSError *error;
            _refreshToken = [[NSString alloc] initWithContentsOfFile:_refreshTokenFile encoding:NSUTF8StringEncoding error:&error];
             
            // If an error occurs while saving the refresh token notify the caller class.
            if (error) {
                [self.gOAuthDelegate errorOccuredWithShortDescription:@"An error occured while loading the refresh token."
                                                      andErrorDetails:[error localizedDescription]];
            }
        }
    }

    Подход здесь такой же, как ранее реализованный. Мы проверяем, существует ли файл с кодом обновления, и загружаем ли мы его в refreshTokenстроку. В противном случае мы сообщаем об ошибке классу вызывающей стороны через метод делегата.

    Следующая задача — проверить, существует ли файл с информацией токена доступа или нет.

    1
    2
    3
    4
    5
    -(BOOL)checkIfAccessTokenInfoFileExists
    {
        // If the access token info file exists, return YES, otherwise return NO.
        return (![[NSFileManager defaultManager] fileExistsAtPath:_accessTokenInfoFile]) ? NO : YES;
    }

    Сделайте то же самое для файла обновления токена.

    1
    2
    3
    4
    5
    -(BOOL)checkIfRefreshTokenFileExists
    {
        // If the refresh token file exists then return YES, otherwise return NO.
        return (![[NSFileManager defaultManager] fileExistsAtPath:_refreshTokenFile]) ? NO : YES;
    }

    Довольно просто, правда?

    Давайте теперь посмотрим, как проверить, является ли токен доступа действительным или нет. Говоря о достоверности, мы имеем в виду, истек ли токен доступа или нет. Google возвращает время, в течение которого токен доступа может сохраняться, вместе с остальными возвращенными данными. Это время выражается в секундах и обычно составляет 3600 секунд или один час. Если срок действия маркера доступа истекает, и мы хотим обменяться данными из службы Google, нам просто нужно обновить его в фоновом режиме и получить новый.

    Наша стратегия будет очень простой. Мы прочтем время создания файла, содержащего информацию о токене доступа, а также время, в течение которого токен доступа должен оставаться в силе. Если время, прошедшее с момента создания файла, больше, чем время жизни, требуется обновление, в противном случае токен доступа остается действительным. Давайте посмотрим все в действии:

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    -(BOOL)checkIfShouldRefreshAccessToken{
        NSError *error = nil;
     
        // Get the time-to-live (in seconds) value regarding the access token.
        int accessTokenTTL = [[_accessTokenInfoDictionary objectForKey:@"expires_in"] intValue];
        // Get the date that the access token file was created.
        NSDate *accessTokenInfoFileCreated = [[[NSFileManager defaultManager] attributesOfItemAtPath:_accessTokenInfoFile error:&error]
                                              fileCreationDate];
         
        // Check if any error occured.
        if (error != nil) {
            [self.gOAuthDelegate errorOccuredWithShortDescription:@"Cannot read access token file's creation date."
                                                  andErrorDetails:[error localizedDescription]];
             
            return YES;
        }
        else{
            // Get the time difference between the file creation date and now.
            NSTimeInterval interval = [[NSDate date] timeIntervalSinceDate:accessTokenInfoFileCreated];
     
            // Check if the interval value is equal or greater than the accessTokenTTL value.
            // If that's the case then the access token should be refreshed.
            if (interval >= accessTokenTTL) {
                // In this case the access token should be refreshed.
                return YES;
            }
            else{
                // Otherwise the access token is valid.
                return NO;
            }
        }
    }

    Таким образом, в случае обновления токена доступа мы возвращаем YES, в противном случае мы возвращаем NO. Все самоочевидно, поэтому я больше не буду комментировать.

    Существует только один метод, который был исключен из этих шагов, — makeRequest. Этот метод не имеет ничего особенного или особенного, на что я должен указать. Он существует только для того, чтобы не писать один и тот же код каждый раз, когда мы хотим сделать запросы NSURLRequest. Итак, как вы сразу увидите, мы очищаем receivedDataобъект, устанавливая его длину равным нулю, и делаем новый запрос.

    1
    2
    3
    4
    5
    6
    7
    -(void)makeRequest:(NSMutableURLRequest *)request{
        // Set the length of the _receivedData mutableData object to zero.
        [_receivedData setLength:0];
         
        // Make the request.
        _urlConnection = [NSURLConnection connectionWithRequest:request delegate:self];
    }

    Есть еще несколько вещей, которые мы должны добавить в connectionDidFinishLoading:метод, чтобы сделать этот класс более полным. До сих пор мы добавили поддержку для обработки ответов относительно:

    • Неверные запросы
    • Недействительные гранты (например, маркер обновления с истекшим сроком действия)
    • Получение токена доступа

    Мы также должны добавить поддержку для случая неверных учетных данных и для любого другого ответа, который может содержать слово ошибки внутри него. Ответ, содержащий сообщение « Недействительные учетные данные», поступает, когда вызывается API и токен доступа недействителен. В нашей реализации мы всегда проверяем, является ли токен доступа действительным или нет, прежде чем делать какие-либо запросы. Тем не менее, это остается крайним случаем, который мы должны прогнозировать Для любого другого общего случая, когда ответ описывает ошибку, мы будем вызывать errorInResponseWithBodyметод делегата. Итак, в конце connectionDidFinishLoading:добавьте следующий раздел кода:

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    // Check for invalid credentials.
    // This checking is useful when an API is called without prior checking whether the
    // access token is valid or not.
    if ([responseJSON rangeOfString:@"Invalid Credentials"].location != NSNotFound ||
        [responseJSON rangeOfString:@"401"].location != NSNotFound) {
        [self refreshAccessToken];
         
        isAPIResponse = NO;
    }
     
     
    // This is the case where any other error message exists in the response.
    if ([responseJSON rangeOfString:@"error"].location != NSNotFound) {
        [self.gOAuthDelegate errorInResponseWithBody:responseJSON];
        isAPIResponse = NO;
    }

    Если вы сейчас посмотрите на connectionDidFinishLoading:содержание метода, вы увидите все случаи, о которых нам следует позаботиться в отношении ответов Google. Для обычного использования и в целях этого урока, почти ничего не нужно добавлять, кроме одного. Вызов responseFromServiceWasReceived:andResponseJSONAsData:метода делегата. Этот метод будет вызываться каждый раз, когда будет получен ответ о вызове API, который должен быть обработан классом вызывающего, и он не подпадает ни под один из предыдущих случаев. Вот:

    1
    2
    3
    4
    // If execution successfully arrives here then notify the caller class that a response was received.
    if (isAPIResponse) {
        [self.gOAuthDelegate responseFromServiceWasReceived:responseJSON andResponseJSONAsData:_receivedData];
    }

    Было бы полезно (особенно при отладке) также реализовать следующий метод:

    1
    2
    3
    4
    -(void)connection:(NSURLConnection *)connection didReceiveResponse🙁NSURLResponse *)response{
        NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
        NSLog(@"%d", [httpResponse statusCode]);
    }

    httpResponseОбъект содержит возвращаемый код состояния HTTP запроса , который был сделан. Если все катится нормально, значение этого кода равно 200. Поищите в Интернете дополнительную информацию о кодах состояния HTTP, если хотите.


    В некоторых случаях вы можете отменить доступ, предоставленный вами в приложении. Google предоставляет URL-адрес, по которому можно сделать запрос и когда доступ отменяется.

    Откройте GoogleOAuth.hкласс, чтобы объявить новый открытый метод, как показано ниже:

    1
    2
    3
    4
    5
    6
    7
    @interface GoogleOAuth : UIWebView <UIWebViewDelegate, NSURLConnectionDataDelegate>
    @property (nonatomic, strong) id<GoogleOAuthDelegate> gOAuthDelegate;
    -(void)authorizeUserWithClienID:(NSString *)client_ID andClientSecret🙁NSString *)client_Secret
                      andParentView🙁UIView *)parent_View andScopes🙁NSArray *)scopes;
    -(void)revokeAccessToken;
     
    @end

    Теперь перейдите к GoogleOAuth.mфайлу для его реализации:

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    -(void)revokeAccessToken{
        // Set the revoke URL string.
        NSString *revokeURLString = [NSString stringWithFormat:@"https://accounts.google.com/o/oauth2/revoke?token=%@",
                                     [_accessTokenInfoDictionary objectForKey:@"access_token"]
                                     ];
        // Create and make a request based on the URL string.
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:revokeURLString]];
        [self makeRequest:request];
         
        // Now that the request for revoking the access in Google has been made,
        // all local files regarding the access token should be removed as well.
        NSError *error = nil;
        // If the access token info file exists then delete it.
        if ([self checkIfAccessTokenInfoFileExists]) {
            [[NSFileManager defaultManager] removeItemAtPath:_accessTokenInfoFile error:&error];
             
            if (error != nil) {
                // If an error occurs while removing the access token info file then notify the caller class through the
                // next delegate method.
                [self.gOAuthDelegate errorOccuredWithShortDescription:@"Unable to delete access token info file."
                                                      andErrorDetails:[error localizedDescription]];
            }
        }
         
        // Check now if the refresh token file exists and then remove it.
        if ([self checkIfRefreshTokenFileExists]) {
            [[NSFileManager defaultManager] removeItemAtPath:_refreshTokenFile error:&error];
             
            if (error != nil) {
                // In case of an error while removing the file then notify the caller class through the delegate method.
                [self.gOAuthDelegate errorOccuredWithShortDescription:@"Unable to delete refresh token info file."
                                                      andErrorDetails:[error localizedDescription]];
            }
        }
         
         
        if (error == nil) {
            // If no error occured during file removals then use the next delegate method
            // to notify the caller class that the access has been revoked.
            [self.gOAuthDelegate accessTokenWasRevoked];
        }
    }

    Сначала мы готовим строку отзыва URL и делаем запрос. Затем любой файл, связанный с токеном доступа, удаляется и, наконец, accessTokenWasRevokedвызывается метод делегата.


    Наш класс готов и полностью функционален, и если вы используете его сейчас, вы сможете получить токен доступа от Google. Но, не реализовав способ вызова API, единственными данными, которые вы можете получить, является ваша личная информация. Поэтому давайте реализуем наш последний публичный метод, действительно важный и необходимый.

    Перейдите к GoogleOAuth.hфайлу и объявите следующий метод, как вы видите его:

    1
    2
    -(void)callAPI:(NSString *)apiURL withHttpMethod:(HTTP_Method)httpMethod
                        postParameterNames🙁NSArray *)params postParameterValues🙁NSArray *)values;

    Обратите внимание, что любой API, который требует публикации данных в Google, должен быть в паре ключ-значение. Параметры для этого:

    • apiURL : Строка URL, необходимая для вызова API.
    • httpMethod: Желаемый метод HTTP для вызова API. Здесь используется тот enumтип, который мы создали в начале.
    • params : NSArray-объект, содержащий ключи параметров пары ключ-значение.
    • values : NSArray объект, содержащий значения параметров формы пары ключ-значение.

    Теперь включите GoogleOAuth.mфайл и реализуйте метод:

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    -(void)callAPI:(NSString *)apiURL withHttpMethod:(HTTP_Method)httpMethod
                                postParameterNames🙁NSArray *)params
                                postParameterValues🙁NSArray *)values{
         
        // Check if the httpMethod value is valid.
        // If not then notify for error.
        if (httpMethod != httpMethod_GET && httpMethod != httpMethod_POST && httpMethod != httpMethod_DELETE && httpMethod != httpMethod_PUT) {
            [self.gOAuthDelegate errorOccuredWithShortDescription:@"Invalid HTTP Method in API call" andErrorDetails:@""];
        }
        else{
            // Create a string containing the API URL along with the access token.
            NSString *urlString = [NSString stringWithFormat:@"%@?access_token=%@", apiURL, [_accessTokenInfoDictionary objectForKey:@"access_token"]];
            // Create a mutable request.
            NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]];
             
            // Depending on the httpMethod value set the respective property of the request object.
            switch (httpMethod) {
                case httpMethod_GET:
                    [request setHTTPMethod:@"GET"];
                    break;
                case httpMethod_POST:
                    [request setHTTPMethod:@"POST"];
                    break;
                case httpMethod_DELETE:
                    [request setHTTPMethod:@"DELETE"];
                    break;
                case httpMethod_PUT:
                    [request setHTTPMethod:@"PUT"];
                    break;
                     
                default:
                    break;
            }
             
             
            // In case of POST httpMethod value, set the parameters and any other necessary properties.       
            if (httpMethod == httpMethod_POST) {
                // A string with the POST parameters should be built.
                // Create an empty string.
                NSString *postParams = @"";
                // Iterrate through all parameters and append every POST parameter to the postParams string.
                for (int i=0; i<[params count]; i++) {
                    postParams = [postParams stringByAppendingString:[NSString stringWithFormat:@"%@=%@",
                                                                      [params objectAtIndex:i], [values objectAtIndex:i]]];
                     
                    // If the current parameter is not the last one then add the "&" symbol to separate post parameters.
                    if (i < [params count] - 1) {
                        postParams = [postParams stringByAppendingString:@"&"];
                    }
                }
                 
                // Set any other necessary options.
                [request setHTTPBody:[postParams dataUsingEncoding:NSUTF8StringEncoding]];
                [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
            }
             
             
            // Make the request.
            [self makeRequest:request];
        }
    }

    В этом методе нет ничего сложного, что нужно обсуждать.

    Наш класс теперь готов к работе. Давайте перейдем к нашему демонстрационному приложению, чтобы попробовать его!


    В начале реализации этого проекта, все еще работая с Interface Builder, мы добавили табличное представление в наш проект, и теперь пришло время его настроить. Итак, сначала перейдите к ViewController.hфайлу и примите следующие протоколы, как вы видите в выделенном коде:

    1
    2
    3
    4
    5
    6
    7
    @interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
    @property (weak, nonatomic) IBOutlet UITableView *table;
     
    - (IBAction)showProfile:(id)sender;
    - (IBAction)revokeAccess:(id)sender;
     
    @end

    Помимо двух вышеуказанных протоколов, нам также необходимо принять наш собственный протокол относительно нашего класса. Итак, в верхней части файла импортируйте GoogleOAuth.hфайл и также примите GoogleOAuthDelegateпротокол:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #import <UIKit/UIKit.h>
    #import "GoogleOAuth.h"
    @interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, GoogleOAuthDelegate>
    @property (weak, nonatomic) IBOutlet UITableView *table;
     
    - (IBAction)showProfile:(id)sender;
    - (IBAction)revokeAccess:(id)sender;
     
    @end

    Теперь перейдите к ViewController.hфайлу. В закрытом разделе интерфейса добавьте эти два изменяемых массива, так как они нужны нам для хранения информации профиля пользователя, которая будет показана позже в табличном представлении:

    1
    2
    3
    4
    @interface ViewController ()
    @property (nonatomic, strong) NSMutableArray *arrProfileInfo;
    @property (nonatomic, strong) NSMutableArray *arrProfileInfoLabel;
    @end

    Фактически, он arrProfileInfoLabelбудет содержать данные, которые описывают каждое поле информации профиля, возвращаемой Google. Эти данные будут помещены в detailTextLabel каждой ячейки.

    Нам также нужен объект нашего недавно созданного класса. Итак, прямо под объявлениями массива добавьте это:

    1
    2
    3
    4
    5
    6
    @interface ViewController ()
    @property (nonatomic, strong) NSMutableArray *arrProfileInfo;
    @property (nonatomic, strong) NSMutableArray *arrProfileInfoLabel;
    @property (nonatomic, strong) GoogleOAuth *googleOAuth;
     
    @end

    Давайте инициализируем наши объекты и установим наших делегатов. Войдите в viewDidLoadметод для инициализации обоих массивов и googleOAuthобъекта, а также для установки делегатов, включая делегата табличного представления.

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    — (void)viewDidLoad
    {
        [super viewDidLoad];
     
        [_table setDelegate:self];
        [_table setDataSource:self];
         
        _arrProfileInfo = [[NSMutableArray alloc] init];
        _arrProfileInfoLabel = [[NSMutableArray alloc] init];
         
        _googleOAuth = [[GoogleOAuth alloc] initWithFrame:self.view.frame];
        [_googleOAuth setGOAuthDelegate:self];
         
    }

    Теперь мы должны создать методы табличного представления. Мы приведем в действие два массива, которые мы объявили ранее, хотя по ним еще нет данных. В следующем разделе кода я предоставлю все необходимые вам методы.

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    -(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
        return 1;
    }
     
     
    -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
        return [_arrProfileInfo count];
    }
     
     
    -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
        static NSString *CellIdentifier = @»Cell»;
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: CellIdentifier];
         
        if (cell == nil) {
            cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
             
            [cell setSelectionStyle:UITableViewCellSelectionStyleNone];
            [cell setAccessoryType:UITableViewCellAccessoryNone];
             
            [[cell textLabel] setFont:[UIFont fontWithName:@"Trebuchet MS" size:15.0]];
            [[cell textLabel] setShadowOffset:CGSizeMake(1.0, 1.0)];
            [[cell textLabel] setShadowColor:[UIColor whiteColor]];
             
            [[cell detailTextLabel] setFont:[UIFont fontWithName:@"Trebuchet MS" size:13.0]];
            [[cell detailTextLabel] setTextColor:[UIColor grayColor]];
        }
         
        [[cell textLabel] setText:[_arrProfileInfo objectAtIndex:[indexPath row]]];
        [[cell detailTextLabel] setText:[_arrProfileInfoLabel objectAtIndex:[indexPath row]]];
         
        return cell;
    }
     
     
    -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
        return 60.0;
    }

    Пришло время использовать то, что мы создали, реализовав два метода IBAction, которые мы создали в начале проекта. Начиная с showProfile:метода, мы будем вызывать authorizeUserWithClienID: andClientSecret: andParentView: andScopes:класс GoogleOAuth, который запустит процесс авторизации. Давайте реализуем это:

    1
    2
    3
    4
    5
    6
    7
    - (IBAction)showProfile:(id)sender {
        [_googleOAuth authorizeUserWithClienID:@"YOUR_CLIENT_ID"
                               andClientSecret:@"YOUR_CLIENT_SECRET"
                               andParentView:self.view
                               andScopes:[NSArray arrayWithObjects:@"https://www.googleapis.com/auth/userinfo.profile", nil]
         ];
    }

    Обязательно установите свой собственный идентификатор клиента и секретные значения клиента.

    Давайте реализуем revokeAccess:метод IBAction сейчас. Это всего лишь одна строка кода:

    1
    2
    3
    - (IBAction)revokeAccess:(id)sender {
        [_googleOAuth revokeAccessToken];
    }

    Наконец, единственное, что мы оставили, это методы делегатов. Используя первый метод IBAction, мы запускаем поток авторизации. Используя методы делегата, мы будем контролировать все и извлекать нужные нам данные. Итак, давайте реализуем их один за другим.

    Начнем с authorizationWasSuccessful:метода. Этот метод вызывается после того, как новый токен доступа успешно получен или существующий токен все еще действителен. Итак, мы готовы продолжить вызов API. Это довольно просто, так как мы просто запрашиваем профиль пользователя, и нет данных для публикации.

    1
    2
    3
    4
    5
    -(void)authorizationWasSuccessful{
        [_googleOAuth callAPI:@"https://www.googleapis.com/oauth2/v1/userinfo"
                withHttpMethod:httpMethod_GET
                postParameterNames:nil postParameterValues:nil];
    }

    Продолжайте с accessTokenWasRevoked:методом. Мы просто покажем пользователю предупреждение и очистим массивы и содержимое таблицы.

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    -(void)accessTokenWasRevoked{
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@""
                                                        message:@"Your access was revoked!"
                                                       delegate:self cancelButtonTitle:nil otherButtonTitles:@"OK", nil];
        [alert show];
         
        [_arrProfileInfo removeAllObjects];
        [_arrProfileInfoLabel removeAllObjects];
         
        [_table reloadData];   
    }

    Для методов обработки ошибок мы будем просто регистрировать сообщения об ошибках и все. Вот они оба:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    -(void)errorOccuredWithShortDescription:(NSString *)errorShortDescription andErrorDetails🙁NSString *)errorDetails{
        NSLog(@"%@", errorShortDescription);
        NSLog(@"%@", errorDetails);
    }
     
     
    -(void)errorInResponseWithBody:(NSString *)errorMessage{
        NSLog(@"%@", errorMessage);
    }

    Наконец, самый важный метод делегата, при котором мы получаем ответ Google с информацией о профиле пользователя и заполняем наши массивы. Мы проверяем, существует ли поле family_name в ответе JSON, чтобы убедиться, что информация о профиле пользователя существует в ответе.

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    -(void)responseFromServiceWasReceived:(NSString *)responseJSONAsString andResponseJSONAsData🙁NSData *)responseJSONAsData{
        if ([responseJSONAsString rangeOfString:@"family_name"].location != NSNotFound) {
            NSError *error;
            NSMutableDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:responseJSONAsData
                                                                              options:NSJSONReadingMutableContainers
                                                                                error:&error];
            if (error) {
                NSLog(@"An error occured while converting JSON data to dictionary.");
                return;
            }
            else{
                if (_arrProfileInfoLabel != nil) {
                    _arrProfileInfoLabel = nil;
                    _arrProfileInfo = nil;
                    _arrProfileInfo = [[NSMutableArray alloc] init];
                }
                 
                _arrProfileInfoLabel = [[NSMutableArray alloc] initWithArray:[dictionary allKeys] copyItems:YES];
                for (int i=0; i<[_arrProfileInfoLabel count]; i++) {
                    [_arrProfileInfo addObject:[dictionary objectForKey:[_arrProfileInfoLabel objectAtIndex:i]]];
                }
                 
                [_table reloadData];
            }
        }
    }

    Теперь мы готовы. Давайте попробуем это!


    Запустите проект и позвольте появиться iPhone Simulator. Изначально табличное представление пустое, поскольку данных не существует. Нажмите кнопку « Мой профиль» и введите свои учетные данные, чтобы войти в свою учетную запись Google. В следующем окне разрешите приложению получить доступ к информации вашего профиля и продолжить. Как только вы вернетесь к представлению таблицы, вы увидите, что ваши личные данные отображаются на экране!

    Прекратите запуск приложения и начните снова. Нажмите еще раз на кнопку « Мой профиль» и обратите внимание, что на этот раз не будет запрашиваться никаких учетных данных. Вместо этого ваша личная информация извлекается и будет отображаться в виде таблицы.

    Затем попробуйте отозвать свой доступ и снова зайдите в свой профиль. На этот раз вам будет предложено разрешение еще раз.

    gt5_final

    Благодаря этому руководству нам удалось увидеть, как на самом деле работает протокол OAuth 2.0. Был реализован простой, но полностью работающий пользовательский класс, состоящий из инструмента многократного использования. Надеемся, что этот инструмент полезен для каждого программиста, который хочет изучить и использовать сервисы, предоставляемые Google. Конечно, новые функции и возможности могут быть добавлены в любое время, и вы можете улучшить его или изменить по своему желанию.

    В этом руководстве я попытался охватить все аспекты, необходимые для реализации сервисов Google с OAuth 2.0. В будущем уроке мы будем использовать этот класс в реальном примере при работе с календарем Google. А пока поиграйте и почувствуйте вкус протокола OAuth 2.0!