Пожалуйста, опишите ошибку

Нашли баг? Помогите нам его исправить, заполнив эту форму

Новое в блоге: Анимация Skeleton

Android Wear: авторизация без пароля

Владислав Герасименко
Android-разработчик

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

android-wear-copy-3

Идея авторизации без пароля через Android Wear устройство заключена в том, чтобы разработать небольшую систему, которая позволит пользователю приложения Android использовать «умные часы» вместо пароля, а точнее его уникальные данные (например Android ID устройства).

Предположим, есть Android-приложение, в котором предусмотрена авторизация посредством отправки логина и пароля на сервер. В функционал этого приложения можно добавить функцию авторизации через Wear-устройство. Когда функция активирована, при логине будет происходить запрос к часам. Часы отправляют на смартфон уникальный Android ID, который отсылается на сервер вместе с логином для авторизации. Перед включением функции, необходимо закрепить Android ID Wear-устройства за аккаунтом пользователя, а при каждой авторизации можно отправлять флаг, является ли пароль ключом Wear-устройства.

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

Один из способов реализации идеи

Наглядно продемонстрирую один из способов реализации авторизации без пароля. Лучше всего показать работу системы схематично.

Первая схема — процесс регистрации Android Wear-устройства на сервере, т.е. закрепление его уникального id за аккаунтом пользователя:

7

Подробнее:

  1. Отправка запроса на Wear-устройство. В запросе логин пользователя. Wear-устройство принимает запрос и выводит окно подтверждения на отправку уникального ID для регистрации пользователя:
  2. 12
  3. Если пользователь подтверждает отправку, Wear-устройство формирует запрос обратно к смартфону с уникальным ID.
  4. Далее, приложение смартфона отправляет Wear-ID на сервер для его закрепления за аккаунтом пользователя.

Вторая схема — это тот случай, когда пользователь захотел выполнить авторизацию без пароля, используя Android Wear-устройство. Для этого лучше сначала убедиться, что введенный логин зарегистрирован:

8

Подробнее:

  1. Запрос на сервер с логином для проверки регистрации.
  2. Ответ от сервера. Можно сделать проверку наличия закрепленного Wear ID. Если не закреплен — уведомить пользователя о невозможной авторизации через Android Wear.

Третья схема — процесс авторизации через Android Wear:

9

Подробнее:

  1. Происходит запрос с логином пользователя к часам на получение Wear ID. В приложении на часах выводится диалог подтверждения авторизации «Подтвердить авторизацию пользователя Jack17?».
  2. В случае подтверждения авторизации, происходит запрос обратно к смартфону с Wear ID.
  3. Wear ID отправляется на сервер вместе с логином и флагом, который указывает, что авторизация идет с помощью Android Wear.
  4. Сервер проверяет наличие закрепленного за пользователем Android Wear ID и формирует соответствующий ответ.

Скриншоты для краткой демонстрации процесса авторизации:

3

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

После того, как мы отправим на сервер проверку на то, что введенный логин зарегистрирован и получим положительный ответ от сервера, инициализируем GoogleApiClient и вызываем метод connect, когда происходит нажатие на кнопку «Авторизация».

private TextView textViewLogin;
private String login;
private GoogleApiClient googleApiClient;

public void initGoogleApiClient() {
    googleApiClient = new GoogleApiClient.Builder(this)
            .addApi(Wearable.API)
            .addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
                @Override
                public void onConnected(Bundle connectionHint) {
                    sendConfirmRequest("/openConfirmAuthActivity", login);
                }

                @Override
                public void onConnectionSuspended(int cause) {
                    
                }
            })
            .addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() {
                @Override
                public void onConnectionFailed(ConnectionResult result) {
                    Toast.makeText(getApplicationContext(), "Connection to the wear is failed!", Toast.LENGTH_SHORT).show();
                }
            })
            .build();
}

public void onAuthButtonClick(View view) {
    login=textViewLogin.getText();
    if (googleApiClient.isConnected()) {
        sendConfirmRequest("/openConfirmAuthActivity", login);
    } else {
        googleApiClient.connect();
    }
}

В callback методе onConnected сразу идет отправка сообщения к Wear-устройству с логином и командой на открытие активности подтверждения. Отправка запроса будет осуществлена на все подключенные Wear-устройства. На каждый запрос будет возвращен callback метод с результатом.

private void sendConfirmRequest(final String path, final String login) {
    NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes(googleApiClient).await();
    // перебор всех подключенных wear-устройств и отправка сообщения на каждое
    for (Node node : nodes.getNodes()) {
        Wearable.MessageApi.sendMessage(
                googleApiClient, node.getId(), path, login.getBytes()).setResultCallback(
                new ResultCallback<MessageApi.SendMessageResult>() {
                    @Override
                    public void onResult(MessageApi.SendMessageResult sendMessageResult) {
                        if (!sendMessageResult.getStatus().isSuccess()) {
                            Toast.makeText(getApplicationContext(), "Ошибка отправки запроса", Toast.LENGTH_SHORT).show();
                        } else {
                            Toast.makeText(getApplicationContext(), "Запрос отправлен, подтвердите авторизацию на wear-устройстве", Toast.LENGTH_SHORT).show();
                        }
                    }
                }
        );
    }
}

Здесь происходит перебор всех подключенных к смартфону Wear-устройств и отправка path строки и логина в виде массива байтов.

Теперь приступим к написанию кода на стороне часов, но мы еще вернемся к коду на смартфоне, когда будем получать ответ. Для начала необходимо создать сервис для получения сообщения от смартфона. Добавим его описание в manifest.

<service android:name=". WearListCallListenerService ">
    <intent-filter>
        <action android:name="com.google.android.gms.wearable.MESSAGE_RECEIVED" />
    </intent-filter>
</service>

Здесь добавляется в фильтр намерений действие MESSAGE_RECEIVED, которое сообщает что сервис WearCallListenerService будет отвечать за получение wearable сообщений.

Создадим класс WearCallListenerService, наследуемый от класса WearableListenerService. Переопределим метод onMessageReceived. Этот метод будет вызываться каждый раз, когда сервис будет улавливать сообщение от смартфона.

public class WearCallListenerService extends WearableListenerService {
    private String MESSAGE_PATH_CONFIRM_AUTH="/openConfirmAuthActivity";
    @Override
    public void onMessageReceived(MessageEvent messageEvent) {
        try {
            // отправка уведомления, если тип сообщения тот, который нужен
            if (messageEvent.getPath().equalsIgnoreCase(MESSAGE_PATH_CONFIRM_AUTH)) {
                String login = new String(messageEvent.getData(), "UTF-8");
                sendNotification(login);
            } else {
                super.onMessageReceived(messageEvent);
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

    private void sendNotification(String login){
        // вписываем активность, которую нужно открыть по нажатию
        Intent intent = new Intent(this, ConfirmAuthActivity.class);
        intent.putExtra("login",login);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent,
                PendingIntent.FLAG_ONE_SHOT);
        // создаем нотификацию
        NotificationCompat.Builder notificationBuilder =
                new NotificationCompat.Builder(this)
                        .setContentTitle("Авторизация")
                        .setContentText("Подтвердите авторизацию для "+login)
                        .setContentIntent(pendingIntent);

        NotificationManagerCompat notificationManager =
                NotificationManagerCompat.from(this);

        // показываем нотификацию
        notificationManager.notify(0, notificationBuilder.build());

    }
}

Опишу подробнее. При каждом получении сообщения проверятся его path. Если он тот, который нам необходим, а именно открытие активности подтверждения авторизации, то получаем логин из массива байтов и создаем нотификацию. Создание и вызов нотификаций на Android Wear ничем не отличаются от их создания в обычном Android приложении. В качестве параметра notificationId используем ноль, так как для нас не столь важно накапливать нотификации.

По нажатию на уведомление будет открываться ConfirmAuthActivity. Опишем фрагменты кода в этой активности. Здесь снова будем использовать Message API и сама идея отправки в точности такая же, как и на смартфоне. Если пользователь нажмет кнопку подтверждения, необходимо получить уникальный Android ID Wear устройства. Для этого создадим отдельный метод.

private String getAndroidId(){
    return Settings.Secure.getString(this.getContentResolver(),
            Settings.Secure.ANDROID_ID);
}

Теперь создадим метод инициализации Google API и обработчики нажатия на кнопки подтверждения и отказа.

// метод инициализации Google Api
private GoogleApiClient googleApiClient;
    private String androidId;
    public void initGoogleApiClient() {
        googleApiClient = new GoogleApiClient.Builder(this)
                .addApi(Wearable.API)
                .addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
                    @Override
                    public void onConnected(Bundle connectionHint) {
                        sendRequest();
                    }

                    @Override
                    public void onConnectionSuspended(int cause) {
                        Toast.makeText(getApplicationContext(), "Ошибка", Toast.LENGTH_SHORT).show();
                    }
                })
                .addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() {
                    @Override
                    public void onConnectionFailed(ConnectionResult result) {
                        Toast.makeText(getApplicationContext(), "Ошибка", Toast.LENGTH_SHORT).show();
                    }
                })
                .build();
    }
    // получаем уникальный AndroidId
    private String getAndroidId(){
        return Settings.Secure.getString(this.getContentResolver(),
                Settings.Secure.ANDROID_ID);
    }

    // событие подтверждения авторизации
    public void onConfirmButtonClick(View view) {
        androidId=getAndroidId();
        if (googleApiClient.isConnected()) {
            sendRequest();
        } else {
            googleApiClient.connect();
        }
    }
    // событие отказа от авторизации
    public void onFailureButtonClick(View view) {
        androidId="";
        if (googleApiClient.isConnected()) {
            sendRequest();
        } else {
            googleApiClient.connect();
        }
    }

    // отправка сообщения
    private void sendRequest() {
        NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes(googleApiClient).await();
        for (com.google.android.gms.wearable.Node node : nodes.getNodes()) {
            Wearable.MessageApi.sendMessage(
                    googleApiClient, node.getId(), "/confirmAuth", androidId.getBytes()).setResultCallback(
                    new ResultCallback<MessageApi.SendMessageResult>() {
                        @Override
                        public void onResult(MessageApi.SendMessageResult sendMessageResult) {
                            if (!sendMessageResult.getStatus().isSuccess()) {
                                Toast.makeText(getApplicationContext(), "Ошибка", Toast.LENGTH_SHORT).show();
                            }
                            else {closeActivity();}
                        }
                    }
            );
        }
    }
    private void closeActivity(){
        this.finish();
    }

При открытии активности в методе onCreate будет вызываться метод инициализации Google API. При нажатии на кнопку подтверждения будет вызываться либо метод connect, либо будет сразу отправляться сообщение. Тоже самое будет происходить при отказе пользователя, только для Android ID будет присваиваться пустая строка. Сам метод отправки ничем не отличается от метода отправки в приложении смартфона, за исключением того, что активность будет закрываться в случае успешной отправки.

Теперь создадим сервис на стороне смартфона, для получения ответа от часов. В manifest записываем ровно то же самое что и для Wear.

<service android:name=". WearListCallListenerService ">
    <intent-filter>
        <action android:name="com.google.android.gms.wearable.MESSAGE_RECEIVED" />
    </intent-filter>
</service>

Создаем сам сервис. В нем будем получать сообщение от Wear-устройства, извлекать из него Android ID, создавать broadcast событие, которое будет отлавливаться в WearAuthActivity, откуда и был отправлен запрос к часам.

public class WearCallListenerService extends WearableListenerService {

    private String MESSAGE_PATH_CONFIRM_AUTH="/confirmAuth";

    @Override
    public void onMessageReceived(MessageEvent messageEvent) {
        try {
            if (messageEvent.getPath().equalsIgnoreCase(MESSAGE_PATH_CONFIRM_AUTH)) {
                String androidId = new String(messageEvent.getData(), "UTF-8");
                Intent intent=new Intent("receiver_wear_confirm_auth");
                intent.putExtra("androidId",androidId);
                sendBroadcast(intent);
            } else {
                super.onMessageReceived(messageEvent);
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

// отправляем broadcast событие для activity авторизации
    public void sendBroadcast(Intent intent) {
        getApplicationContext().sendBroadcast(intent);
    }
}

Теперь отправленный в broadcast Android ID необходимо уловить в WearAuthActivity. Для этого в onResume методе зарегистрируем это broadcast событие и создадим обработчик.

@Override
public void onResume() {
    super.onResume();
    this.registerReceiver(mReceiverBroadcast, new IntentFilter("receiver_wear_confirm_auth"));

}
@Override
public void onDestroy() {
    super.onDestroy();
    this.unregisterReceiver(mReceiverBroadcast);
}

// событие на подтверждение авторизации
private final BroadcastReceiver mReceiverBroadcast = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        String androidId = intent.getStringExtra("androidId");
        if (androidId.isEmpty()) {
            Toast.makeText(getApplicationContext(), "Авторизация не подтверждена!", Toast.LENGTH_SHORT).show();
        } else {
            // отправка введенного логина и Android ID на сервер для авторизации
            login(login, androidId);
        }
    }
};

Если пользователь отклонил авторизацию на Wear-устройстве, то на смартфон придет пустая строка, поэтому, если строка не пуста, то отправляем логин и Android ID для авторизации на сервер. Это завершающий этап.

Заключение

Разработка такого вида авторизации может стать неплохим дополнением к вашему приложению. С помощью Android Wear-устройства можно реализовать не только авторизацию без пароля, но и восстановление забытого пароля. Android Wear может послужить компонентом для двухфакторной аутентификации.

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

Читать и комментировать

Краснодар

Коммунаров, 268,
3 эт, офисы 705, 707

+7 (861) 200 27 34

Хьюстон

3523 Brinton trails Ln Katy

+1 833 933 0204

Москва

+7 (495) 145-01-05