Имитируем браузер (на примере залогинивания из приложения в контакт)
Запись от PainKiller размещена 28.08.2014 в 13:38
Некоторое время назад я задумался насколько реально получить access_token для приложения вконтакте без браузера. Мой вопрос на эту тему на форуме остался без ответа и я начал копать сам. Надо сказать, что решение этой проблемы отняло у меня приличное количество времени, зато в результате я многому научился, чем и спешу поделиться. Как обычно говорят в таких случаях - представляемая информация дается для ознакомления, автор не несет ответственности за её использование в незаконных целях (написание спам-ботов, угон аккаунтов у пользователей и т.п.). Тем более, что она и так доступна в интернете, и я не открываю ничего нового, просто привожу свой опыт.
На самом деле решить эту задачу можно двумя путями – простым и сложным. Начнем со сложного, он первым приходит в голову, и его реализации на различных языках я нагуглил первыми. Он заключается в том, чтобы отследить в консоли браузера и затем имитировать все запросы, с соответствующими заголовками, идущие от клиента серверу. Таким образом, если все сделать правильно, сервер не сможет определить, с кем он общается с реальным человеком за открытым браузером или с программой. С примером такой реализации залогинивания вконтакт на php можно ознакомиться здесь (он не рабочий, но логика действий ясна). Создатели веб-приложений прекрасно знают об этом способе и всячески стараются его осложнить, вставляя в страницы javascript’ы которые динамически изменяют страницу после загрузки, подгружают контент шифрованными ajax запросами и т.д. То есть теоретически этот способ работает всегда, но реализация его на практике может оказаться очень и очень геморной задачей. Тем не менее, я начал с него, и начал на флеше. И тут случился первый облом - нативные классы реализующие общение по http протоколу, релизуют его в урезанном виде, и не дают доступа к нужным заголовкам (см. мой пост) Поэтому я обратился к библиотеке As3httpclientlib, а так как авторизация вконтакте идет по протоколу https, использовать её пришлось в паре с as3crypto. Эта пара оказалась довольно глючной, в какой-то момент на мой очередной запрос перестал приходить ответ от сервера, и я решил пересесть на Node.js, тем более что приложение у меня и так предполагается клиент-серверное (Node.js + AIR). На ноде все оказалось проще, глюков не было, но мне просто не хватило терпения идеально сымитировать все нужные запросы, и контакт начинал меня зацикливать (удалял ранее выданные куки, и редиректил на форму авторизации). Плюс на тот момент я уже узнал о втором более простом способе обмана сервера, и решил освоить его (тем более что он еще пригодится и в других проектах). Этот более простой способ заключается в использовании «безголового браузера» т.е. браузера не имеющего GUI (можно только снимать скриншоты с загружаемых страниц) и управляемого из командной строки. Самый популярный на данный момент проект такого типа это phantomJS я начал с него и он у меня не пошел. Хоть я и оцениваю свои знания JavaScript на твердую четверку, его API оказалось для меня слишком суровым, каждое движение на загружаемой странице там выполняется асинхронно по коллбеку, и при написании даже не сильно сложного кода быстро оказываешься в аду вложенных коллбеков, который взрывает мозг. Поэтому я переключился на более приятную надстройку над фантомом – casperJS, которая работает с асинхронностью в стиле библиотек async и promises и буквально за 20 – 30 минут решил задачу (и это после стольких неудачных заходов!).
И так алгоритм действий следующий (я делал это все на Windows 7):
1. Устанавливаем phantomJS и прописываем путь к его исполняемому экзешнику в переменной среды PATH. Результатом должно быть исполнение команды phantomjs в командной строке без ошибки.
2. Устанавливаем casperjs для него также понадобится python 2.6. Опять же прописываем путь к его исполняемому экзешнику в переменной PATH. Результатом должно быть исполнение команды casperjs в командной строке без ошибки.
3. Запускаем в командной строке скрипт авторизации. Если мы находимся в директории где лежит скрипт, команда, запускается с 3 аргументами и выглядит так casperjs vk.js path login password где vk.js файл с нашим скриптом, path – url до формы авторизации (как он образуется см. ниже в модуле для node.js) login – логин пользователя, password – пароль пользователя. Код скрипта я прокомментировал и привожу ниже:
/* * Внимание это не модуль Node.js, это * скрипт исполняемый casperjs * * @author PainKiller */ var casper = require('casper').create(); var x = require('casper').selectXPath; //Выставляем правильный userAgent т.к. по умолчанию каспер отдает свой, и это может смутить сервер casper.userAgent('Mozilla/5.0 (Windows NT 6.1; WOW64; rv:31.0) Gecko/20100101 Firefox/31.0'); /* * открываем форму авторизации, путь к ней получаем как 1й аргумент из * командной строки, вставляем в форму логин (2й агрумент), и пароль (3й) */ casper.start(casper.cli.get(0), function() { //элементы формы выцепляем по XPath, я воспользовался этим способом, т.к. id элементы этой формы не имеют this.fillXPath('form', { '//*[@id="mcont"]/div/div[2]/form/dl[1]/dd/div/input': casper.cli.get(1), '//*[@id="mcont"]/div/div[2]/form/dl[2]/dd/div/input': casper.cli.get(2) }, true); //casper.capture('vk1c.png'); }); /* * нажимаем кнопку submit */ casper.then(function () { //console.log("submitting..."); this.evaluate(function () { $('form').submit(); }); }); /* * ждем 5 секунд загрузки редиректа, это я перестраховываюсь * может появится еще одно окно подтверждения прав приложения - тогда поступаем с ним также как с предыдущей формой */ casper.wait(5000, function(){ //console.log("wait..."); var url = this.evaluate(function () { return document.URL; }); //выводим в консоль url с полученным токеном console.log(url); //casper.capture('vk2c.png'); //закрываем каспер casper.exit(); }); casper.run();
4. Ручное получение токена без браузера это хорошо, но мы же хотели полностью автоматизировать этот процесс)). Поэтому последний пункт - автоматизация запуска скрипта. Для этого я написал модуль Node.js (я знаю про spookyJS но мне было влом переписывать скрипт, да это и не нужно). Скрипт для каспера я просто запускаю в командной строке как дочерний процесс. Привожу его код:
/* * Модуль автоматической авторизации вконтакте, * методом vklogin в который передается объект user с логином и паролем * пользователя, запускает в командной строке скрипт каспера vk.js, * который возвращает url с access_token * * @author PainKiller */ var exec = require('child_process').exec, child, _user; var p = require('path'); var url = require('url'); var qs = require('querystring'); var util = require("util"); var EventEmitter = require("events").EventEmitter; /* * Данные приложения, через которое происходит авторизация, * необходимы для формирования пути к форме авторизации, * подробнее см. доки https://vk.com/dev/auth_mobile */ var app = { APP_ID : "4039999", APP_SECRET : "HPXVooyt0tZZZ6C8tynsv", SETTINGS : "wall,friends", REDIRECT_URI : "http://oauth.vk.com/blank.html", DISPLAY : "mobile", API_VERSION : "5.24" }; function VKAuthorizer() { EventEmitter.call(this); } // наследуемся от EventEmitter, чтобы иметь возможность // испускать событие когда получим токен util.inherits(VKAuthorizer, EventEmitter); /* * функция авторизации, принимает объект user с двумя полями * login и password, и при получении токена эмитит событие * "token_received", передающее объект с токеном */ VKAuthorizer.prototype.vklogin = function(user) { _user = user; var self = this; var path = "http://oauth.vk.com/authorize?client_id=" + app.APP_ID + "&client_secret=" + app.APP_SECRET + "&scope=" + app.SETTINGS + "&v=" + app.API_VERSION + "&username=" + user.login + "&password=" + user.password + "&redirect_uri=" + app.REDIRECT_URI + "&display=mobile&response_type=token"; console.log(path); var pathToJS = p.dirname(process.mainModule.filename); pathToJS = pathToJS.replace('\server.js', ''); pathToJS += '\\utils\\vk.js'; console.log('pathToJS = ' + pathToJS); var command = 'casperjs ' + pathToJS + ' "' + path + '" ' + user.login + " " + user.password; console.log(command); child = exec(command, function (error, stdout, stderr) { //забираем часть урла от хеша var query = url.parse(stdout.toString()).hash.replace('#', ''); // парсим, и получаем объект с access_token var token = qs.parse(query); self.emit("token_received", token); if (error !== null) { console.log('exec error: ' + error); } }); }; module.exports = exports = VKAuthorizer;
var VKAuthorizer = require('./vkAuthorizer'); var auth = new VKAuthorizer(); … auth.on("token_received", function(token){ //делаем что то с полученым токеном }); auth.vklogin(user);
Автоматический запуск скрипта для каспера можно также делать и напрямую из AIR приложения, для этого есть класс NativeProcess
Есть только один маленький ньюанс. Дело в том, что класс NativeProcess позволяет запускать только экзешники и передавать им аргументы. Для меня это не очень удобно, т.к. нужно знать расположение экзешника casperjs, а при переносе проекта на другую машину оно может изменяться. Поэтому для исполнения скрипта casperJS из под AIR лучше воспользоваться этой библиотекой Она использует прокси-экзешник написанный на C++, который перенаправляет аргументы командной строки из AIR в командную строку, и позволяет вызывать, в том числе и системные команды. Экзешник можно положить в директорию приложения и вызывать его оттуда. Но саму эту либу я еще не использовал, поэтому не знаю насколько это все работоспособно, это только мои теоретические измышления.
ну и небольшой эпилог, на самом деле это очень полезная технология, и если включить фантазию ей можно найти очень интересное применение, не только связанное с написанием спам-ботов (web-scraping, использование из своего приложения каких угодно веб-приложений, автоматизированные тесты веб-страниц и т.д.), буду рад если кто нибудь поделится своими идеями на этот счет.
Всего комментариев 15
Комментарии
28.08.2014 16:24 | |
Прокси-экзешник я так и предполагал искать таким образом из директории приложения, только не привел код.
Если же вы имеете в виду что туда же можно положить экзешники phantomjs и casperjs то меня смушает то что casper должен знать путь до фантома, я нашел в сети пример, где путь до фантома прописывался в скрипте каспера через метод phantom.casperPath(), подробнее можно тут почитать но все равно не уверен, что такую конфигурацию удастся запустить хотя попробовать можно. |
|
Обновил(-а) PainKiller 28.08.2014 в 17:10
(Неточность ответа)
|
28.08.2014 23:33 | |
Цитата:
Устанавливаем casperjs для него также понадобится python 2.6.
|
29.08.2014 10:57 | |
Ну а что делать, это быстрое и комфортное решение, но для его работы требуется окружение с кучей фарша, это его минус согласен)))
|
29.08.2014 12:12 | |
Немного скепсиса все же. "обман приложений" - всегда условно, коряво, дыряво и в итоге непотребно. Но скилл прокачать стоит, да.
|
01.09.2014 03:55 | |
почему webkit отдает user agent огнелиса?
|
04.09.2014 21:00 | |
Я так понял, что так можно любую стенку в магазине, т.е. вконткте угнать? Круто!
|
08.09.2014 11:50 | |
Цитата:
Я так понял, что так можно любую стенку в магазине, т.е. вконткте угнать? Круто!
И в продолжение темы, узнал про еще один вариант "подделки браузера" для node.js - WebDriverJs это адаптация selenium для ноды, асинхронность разруливается с помощью promises. Поддерживает прокси, и много чего другого. Выглядит очень удобно. |
16.09.2014 20:20 | |
Круто. Маилру купила вконтактик. Может быть они починят эту дыру.
|
22.09.2014 22:40 | |
Нет, под владельцем аккаунта вы зайти не сможете, для этого нужно знать логин и пароль от аккаунта.
|
22.09.2014 22:41 | |
Это всего лишь техника получения access_token минуя браузер)) Не более того.
|
09.10.2014 17:45 | |
Еще одну охрененную обертку для phantomjs нашел http://www.nightmarejs.org/ Пожалуй она мне больше всех нравится)))
|
Последние записи от PainKiller
- Имитируем браузер (на примере залогинивания из приложения в контакт) (28.08.2014)
- Поднимаем сервер в облаке (Jelastic + Java + Tomcat + MySQL + BlazeDS) Часть III (03.03.2014)
- Поднимаем сервер в облаке (Jelastic + Java + Tomcat + MySQL + BlazeDS) Часть II (27.02.2014)
- Поднимаем сервер в облаке (Jelastic + Java + Tomcat + MySQL + BlazeDS) Часть I (27.02.2014)