воскресенье, 31 января 2010 г.

Vkontakte: безопасная передача данных от клиента к серверу в iFrame приложениях

Не так давно Vkontakte ввело возможность разработки iFrame приложений. В связи с этим у многих разработчиков встал вопрос как защитить данные, передаваемые между приложением и api.

Я бы хотел предложить свое решение:
Защиту данных можно разделить на следующие аспекты:

1. Подтверждение информации о пользователе.
Чтобы получить достоверные подписанные данные о пользователе, необходимо использовать первый запрос к API в настройках приложения.
method=getProfiles&uids={viewer_id}&format=json&v=2.0
Тем самым мы получаем несколько параметров, один из которых нужный нам - viewer_id, а также сигнатуру данных (auth_key), подписанных секретным ключом, который гарантирует нам верность данных.
Т.е. на следующих этапах мы можем использовать информацию о пользователе, не боясь что данные были подменены.

2. Передача данных от пользователя к приложению
Чтобы скрыть секретный ключ и при этом иметь возможность передать данные на сервер придется использовать промежуточное звено - наш ajax обработчик.
Клиентский JavaScript:
function secureSend(_data, _callback) {
  $.ajax({
   type: "POST",
   url: "ajax.php",
   data: (_data),
   dataType: "json",
   success: function(data) {
     if(data.error != undefined) {
        error(data.error);
        return false;
     } else {
      _callback(data.response);
     }
   }
 });
}
На сервере же расположить скрипт обрабатывающий только обращения к API, которые пересылают данные от пользователя к серверу. Разрешить можно все действия, т.к. пользователя мы проверили на предыдущем этапе и уже тут он волен сколько угодно раз пересылать свои голоса к приложению :)
ajax.php, серверный скрипт
$vk_allow_methods = array("secure.withdrawVotes", "secure.getBalance");
$q = $_POST;
$d = "";
if(in_array($q['method'], $vk_allow_methods)) {
  $d = vkSend($q['method']);
}

function vkSend($method) {
  $server = "http://api.vkontakte.ru/api.php?";
  $key = "000";
  $aid = 1;
  
  $qr = array();
  $qr[] = 'method='.$method;
  $qr[] = 'api_id='.$aid;
  $qr[] = 'v=2.0';
  $qr[] = 'format=JSON';
  $qr[] = 'timestamp='.time();
  $qr[] = 'random='.rand(1, 999999);
  $qr[] = 'test_mode=1';
  sort($qr);
  
  $url4sign = implode("", $qr).$key;
  $sign     = md5($url4sign);
  $url      = implode("&", $qr)."&sig=".$sign;
  
  return file_get_contents($server.$url);
}

echo $d;

3. Передача данных от приложения к пользователю.
Такие события не должны ни в коем случае инициироваться клиентским кодом. Если необходимо перевести голоса со счета приложения на счет пользователя, то лучше воспользоваться запароленым скриптом, вызывающимся по cron или вручную администратором.

16 комментариев:

  1. А как сделать кнопку, что бы она списывала голос из приложения, а затем переходила на другую страничку iframe приложения? Помогите пожалуйста.

    ОтветитьУдалить
  2. Эмм, а перезапись document.location.href в колбэке запроса не прокатывает?

    function secureSend(_data, _callback) {
    $.ajax({
    type: "POST",
    url: "ajax.php",
    data: (_data),
    dataType: "json",
    success: function(data) {
    if(data.error != undefined) {
    return false;
    } else {
    document.location.href="ok.html";
    }
    }
    });
    }

    ОтветитьУдалить
  3. Работает кнопка только на первой страничке(((
    на другой, где нужно пишет invalid user id
    думаю, потому что запрос к api на других страничках не работает.

    первый запрос к API в настройках приложения.
    method=getProfiles&uids={viewer_id}&format=json&v=2.0

    ОтветитьУдалить
  4. Ко всем ссылкам автоматически добавлять параметры из $_GET (auth_key, viewer_id, api_url).
    Первый раз данные будут получены от Вконтакте, далее они будут передаваться по ссылке.

    ОтветитьУдалить
  5. с главной все работает, а с других страничек не работает. в чем может быть проблемма?

    ОтветитьУдалить
  6. Еще раз:
    <?
    //index.php
    function getUriParam() {
    return 'api_url='.$_GET['api_url'].'&api_id='.$_GET['api_id'].'&viewer_id='.$_GET['viewer_id'].'&auth_key='.$_GET['auth_key'];
    }
    echo '<a href="link.php?do=something&'.getUriParam().'">Другая страница</a>';
    ?>

    <?
    //link.php?do=something&api_url=...&api_id=...&viewer_id=...&auth_key=...
    print_r($_GET);
    ?>

    ОтветитьУдалить
  7. Т.е. другой странице эти параметры нужно передать как часть ссылки, чтобы можно было их использовать повторно.

    ОтветитьУдалить
  8. У меня ситуация немного другая. Как файл (картинка) загружается, автоматически переходит на другую страничку.

    Выглядит это так:
    function compf() {
    window.location.href="crop.php";
    }

    ОтветитьУдалить
  9. 1. Разбейте location.href на составляющие:
    - самостоятельно, используя arr.split("&")
    - использую функционал вконтакте:
    VK.init(function() {
      VK.loadParams(document.location.href);
      }, function(){}
    );
    2. Добавьте разбитые параметры к location:
    window.location.href="crop.php?viewer="+VK.params.viewer_id+"...";

    ОтветитьУдалить
  10. Алексей, а как передаются в приложение данные с урла.

    Пользователь запрашивает страницу:
    vkontakte.ru/appXXXXXXX#comment/&f_id=15

    а мне в приложение передается строка:
    mysite.ru/vk/comment/&f_id=15.

    Как получить этот кусок:
    comment/&f_id=15

    ОтветитьУдалить
  11. Где физический находится приложение? mysite.ru/vk/ ?
    Если так, то нужно использовать mod_rewrite и в скрипте парсить серверную переменную $_SERVER['QUERY_STRING'].
    Ну или переделать, если не охота использовать мод реврайт, чтобы параметры передавались в виде ?act=comment&f_id=15

    ОтветитьУдалить
  12. Эм... глупый вопрос наверно задам. Но я новичёк во всём этом. Сделал как написано, скрипт на сервере, код джава на главной странице и в настройках первый запрос стоит... А что дальше? Как например передать запрос теперь на получение счёта пользователя в приложении? Очень прошу, подскажите.

    ОтветитьУдалить
  13. Андрей, обратитесь к документации
    http://vkontakte.ru/pages.php?o=-1&p=%D0%9E%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5+%D0%BC%D0%B5%D1%82%D0%BE%D0%B4%D0%BE%D0%B2+API
    или к официальной группе iframe приложений, там вам помогут
    http://vkontakte.ru/club15957347

    ОтветитьУдалить
  14. Здравствуйте, возможно не лучшее место чтобы поднять вопрос, но гугл не помог, а тут вроде как раз подходящая тема.

    Использую аналогичный jquery-ajax запрос, и вроде бы все ничего, но в опере вылезает ошибка. Что то в духе того что не хватает прав. Как я понял свзано это с тем что она запрещает кросс-доменные запросы. Без iframe, в отдельном окне, приложение работает везде, в том числе и в Опере.

    На сколько я понял какая то хитрость связанная с тем что Опера считает что запрос из вконтактовского iframe идет на другой домен. Может быть есть какая то информация на этот счет и как все-таки заставить его работать?

    ОтветитьУдалить
  15. Когда делал приложение, то проверял и в Опере. Все работало.
    Вы надеюсь используете jsonp запросы?
    Также советую прочитать 7 пункт на этой странице http://vkontakte.ru/page-15957347_10972154, возможно в этом проблема.

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

    Достоверным здесь будет только viewer_id, api_id - больше ничего.

    А зачем это пром. звено, если можно все словить в скрипте на сервере, через GET, а далее проверить ?

    ОтветитьУдалить