Создаём универсальный класс взаимодействия с вашим сервером

Уроки по PHP, Javascript и т.п.
Ответить
AlexProger
Сообщения: 6
Зарегистрирован: 01 сен 2012, 16:42

Создаём универсальный класс взаимодействия с вашим сервером

Сообщение AlexProger »

Делал я однажды заказ для заказчика (для себя ничего не люблю делать :cry: ) и понадобилось мне взаимодействовать с сервером и тут появилось 2 вопроса :
  • 1. Как это сделать удобнее
    2. Как это сделать безопаснее
Условия :
  • 1. На сервере 3 скрипта. (config.php, vkapi.class.php, core.php)
    2. Взаимодействовать надо только со скриптом core.php
    3. Надо чтобы были минимальные сложности в использовании
Приступаем к решению :
[Шаг 1: создаём среду для работы скрипта core.php]
Файл config.php :

Код: Выделить всё

 <?php/*@Author : Alexandr Leutin {AlexProger, Xikke}*/define("DB_USER","..."); //Имя пользователя БДdefine("DB_PASS","...");  //Пароль БДdefine("DB_NAME","..."); //Имя БДdefine("DB_HOST","localhost"); define("API_ID","...");  //ID прилы в вкdefine("API_SECRET","........."); //Секретный ключ прилы function Connect(){    mysql_connect(DB_HOST, DB_USER, DB_PASS) or die (mysql_error());    mysql_select_db(DB_NAME) or die (mysql_error());    mysql_query("SET NAMES 'utf8'");    }?> 
Единственное что хотелось бы выделить в данном скрипте это то что данные хранятся не в переменных, а в константах объявленных через define
Файл vkapi.class.php :

Код: Выделить всё

 <?php /** * VKAPI class for vk.com social network * * @package server API methods * @link http://vk.com/developers.php * @autor Oleg Illarionov * @version 1.0 */ class vkapi {    var $api_secret;    var $app_id;    var $api_url;        function vkapi($app_id, $api_secret, $api_url = 'api.vk.com/api.php') {        $this->app_id = $app_id;        $this->api_secret = $api_secret;        if (!strstr($api_url, 'http://')) $api_url = 'http://'.$api_url;        $this->api_url = $api_url;    }        function api($method,$params=false) {        if (!$params) $params = array();         $params['api_id'] = $this->app_id;        $params['v'] = '3.0';        $params['method'] = $method;        $params['timestamp'] = time();        $params['format'] = 'json';        $params['random'] = rand(0,10000);        ksort($params);        $sig = '';        foreach($params as $k=>$v) {            $sig .= $k.'='.$v;        }        $sig .= $this->api_secret;        $params['sig'] = md5($sig);        $query = $this->api_url.'?'.$this->params($params);        $res = file_get_contents($query);        return json_decode($res, true);    }        function params($params) {        $pice = array();        foreach($params as $k=>$v) {            $pice[] = $k.'='.urlencode($v);        }        return implode('&',$pice);    }}?> 
[Шаг 2 : готовимся к написанию скрипта core.php]
Для начала определим задачи и список функций выполняемых скриптом.
  • 1. Авторизация пользователя
    2. Получение его данных из БД
    3. Запись данных в БД
Определив задачи нам нужно спроектировать БД которая бы устраивала нас. Благо я это уже сделал :

Код: Выделить всё

 CREATE TABLE IF NOT EXISTS `users` (  `id` int(9) auto_increment,  `uid` int(9),  `money` int(6) default '100',  `rait` int(8) default '0',  `last_enter` int(15),  PRIMARY KEY  (`id`),  UNIQUE KEY `uid` (`uid`)) ENGINE=MyISAM  DEFAULT CHARSET=utf8; 
Теперь я опишу что обозначают эти поля :
  • id - номер в базе
    uid - ID пользователя в вк
    money - кол-во денег в приле
    rait - рейтинг в приле
    last_enter - UNIX время последнего захода (защитимся от захода в прилу через каждые 5 секунд)
Теперь определимся как скрипт будет узнавать что мы от него хотим?
Естественно передавая переменную act вот варианты её значения :
  • AUTH_USR - пользователь хочет авторизироваться
    GET_USER_INFO - получаем данные о юзере
    UPDATE_USER_INFO - обновляем данные о юзере
[шаг 3: реализуем скрипт core.php]
Итак часть 1 : подключаем ранее написанные файлы :

Код: Выделить всё

 include 'config.php';require 'vkapi.class.php'; 
Здесь обсуждать нечего если вы хоть чуть - чуть знакомы с PHP
Теперь получим обязательные данные которые нужны скрипту :

Код: Выделить всё

 $uid = $_POST['viewer_id'];$auth = $_POST['auth_key'];$act = $_POST['act'];$key = $_POST['key']; 
Теперь я опишу что означают эти переменные :
  • $uid - ID пользователя в вк
    $auth - auth_key пользователя
    $act - наша переменная обозначающая что мы хотим делать
    $key - ключ защиты переменной act
Здесь я хотел бы выделить последнюю переменную :
$key - это хэш защищающий от подмены не только $act, но и $uid и $auth.
Вот формула (защита идёт через MD5, хотя мы спокойно могли бы использовать и SHA1 и другие алгоритмы защиты данных, но такой задачи нам не ставилось и мы не ищем лёгких путей :mrgreen: )
Нам пора использовать полученные данные, я решил не расписывать каждый шаг и представил каркас ниже :

Код: Выделить всё

 <?phpinclude 'config.php';require 'vkapi.class.php';Connect();/*    @desc: Загрузка переменных*/$uid  = $_POST['uid'];$auth = $_POST['auth'];$act  = $_POST['act'];$key  = $_POST['key'];/*    @desc: Обработка данных*/if ($auth == md5(API_ID.'_'.$uid.'_'.API_SECRET)){    if (md5(md5($act).md5($auth).md5($uid)) == $key){        switch($act)        {            case "AUTH_USR":            //Авторизация            break;            case "GET_USER_INFO":            //Получение данных            break;            case "UPDATE_USER_INFO":            //Обновление данных            break;            default:            //Неизвестный акт            break;        }    }else{    //Ошибка в key (скорее всего подмена данных        }}else{//Ошибка в auth_key }?> 
Если вы не поняли что делает этот каркас, значит вам надо учить PHP
Итак продолжим реализацию :
Теперь нам надо записать пользователя в БД, код представлен ниже :

Код: Выделить всё

 $result = mysql_query('INSERT INTO `users` (`uid`, `last_enter`) VALUES ("'.$uid.'","'.time().'") ON DUPLICATE KEY UPDATE  `last_enter` = "'.time().'";');if (!$result){//Ошибка mysql запроса выводим её в ответ :exit('{"error":"1","desc":"mysql error '.mysql_error().'}');}else{//Выводим что всё хорошоexit('{"error":"0","auth":"1","hash":"'.md5(md5('1_auth_1')).'"}');} 
Обсудим что я сотворил выше :D
Я делаю запрос на занесение пользователя в базу и в случае ошибки выдаю пользователю в виде JSON ответа.
Думаю ничего сложного в этом нет поэтому приступим к рассмотрению второго вида акта : GET_USER_INFO
Я не буду разъяснять что мы делаем в этом акте т.к. это за меня сделал Александр (Тыц чтобы посмотреть урок) поэтому вот код :

Код: Выделить всё

 $result = mysql_query('SELECT * FROM `users` WHERE `uid`='.$uid.';');if (!$result){//Ошибка mysql запроса выводим её в ответ :exit('{"error":"1","desc":"mysql error '.mysql_error().'}');}else{$data = mysql_fetch_assoc($result);     //mysql_fetch_assoc потому что нам надо обращаться к переменныем таким способом : $data['money'];//Создаём переменные которые надо вывести : $money = $data['money'];$rait  = $data['rait'];$hash  = md5(md5($money).md5($rait));//Выводим результатexit('{"error":"0","money":"'.$money.'","rait":"'.$rait.'","hash":"'.$hash.'"}');} 
Теперь напишем третий акт : UPDATE_USER_INFO.
Сейчас я немного отойду от темы и мы поговорим о безопасности этого акта.
Данный акт требует дополнительные данные (кол-во добавляемых едениц) и нам надо защитить эти данные. Поэтому мы будем принимать к этому методу не 2 параметра, а 3.
Параметры :
  • 1. money - это сколько надо добавить денег
    2. rait - это сколько надо добавить рейтинга
    3. checker - это хэш данных (защищает только money и rait и привязан к auth_key)
Формула составления хэша :
md5(money + auth + rait);
Вот код третьего акта :

Код: Выделить всё

 $money = $_POST['m_add']; //Внимание : я использовал именование m_add, r_add вместо money и rait$rait  = $_POST['r_add']; //$hash  = $_POST['c_add']; //c_add это checkerif (md5($money.$auth.$rait) == $hash){    $result = mysql_query('UPDATE `users` SET `money` = `money` + "'.$money.'", `rait` = `rait` + "'.$rait.'" WHERE `uid`='.$uid.';');    if (!$result)    {        exit('{"error":"1","desc":"mysql error '.mysql_error().'}');    }else{        //Обновляем данные и выводим их        $result_2 = mysql_query('SELECT * FROM `users` WHERE `uid`='.$uid.';');        if (!$result_2){            exit('{"error":"1","desc":"mysql error '.mysql_error().'}');        }else{            //Выводим обновлённые данные [код из акта 2]            $data = mysql_fetch_assoc($result);     //mysql_fetch_assoc потому что нам надо обращаться к переменныем таким способом : $data['money'];            //Создаём переменные которые надо вывести :             $money = $data['money'];            $rait  = $data['rait'];            $hash  = md5(md5($money).md5($rait));            //Выводим результат            exit('{"error":"0","money":"'.$money.'","rait":"'.$rait.'","hash":"'.$hash.'"}');        }    }}else{//Попытка обманаexit('{"error":"1","desc":"invalid hash"}');} 
Готово! Перед тем как приступить к клиенту напишем полный код файла core.php :

Код: Выделить всё

 <?phpinclude 'config.php';require 'vkapi.class.php';Connect();/*    @desc: Загрузка переменных*/$uid  = $_POST['uid'];$auth = $_POST['auth'];$act  = $_POST['act'];$key  = $_POST['key'];/*    @desc: Обработка данных*/if ($auth == md5(API_ID.'_'.$uid.'_'.API_SECRET)){    if (md5(md5($act).md5($auth).md5($uid)) == $key){        switch($act)        {            case "AUTH_USR":            //Авторизация                $result = mysql_query('INSERT INTO `users` (`uid`, `last_enter`) VALUES ("'.$uid.'","'.time().'") ON DUPLICATE KEY UPDATE  `last_enter` = "'.time().'";');                if (!$result){                    //Ошибка mysql запроса выводим её в ответ :                    exit('{"error":"1","desc":"mysql error '.mysql_error().'}');                }else{                    //Выводим что всё хорошо                    exit('{"error":"0","auth":"1","hash":"'.md5(md5('1_auth_1')).'"}');                }            break;            case "GET_USER_INFO":            //Получение данных                $result = mysql_query('SELECT * FROM `users` WHERE `uid`='.$uid.';');                if (!$result){                    //Ошибка mysql запроса выводим её в ответ :                    exit('{"error":"1","desc":"mysql error '.mysql_error().'}');                }else{                    $data = mysql_fetch_assoc($result);     //mysql_fetch_assoc потому что нам надо обращаться к переменныем таким способом : $data['money'];                    //Создаём переменные которые надо вывести :                     $money = $data['money'];                    $rait  = $data['rait'];                    $hash  = md5(md5($money).md5($rait));                    //Выводим результат                    exit('{"error":"0","money":"'.$money.'","rait":"'.$rait.'","hash":"'.$hash.'"}');            }            break;            case "UPDATE_USER_INFO":            //Обновление данных                $money = $_POST['m_add']; //Внимание : я использовал именование m_add, r_add вместо money и rait                $rait  = $_POST['r_add']; //                $hash  = $_POST['c_add']; //c_add это checker                if (md5($money.$auth.$rait) == $hash){                $result = mysql_query('UPDATE `users` SET `money` = `money` + "'.$money.'", `rait` = `rait` + "'.$rait.'" WHERE `uid`='.$uid.';');                if (!$result)                {                    exit('{"error":"1","desc":"mysql error '.mysql_error().'}');                }else{                    //Обновляем данные и выводим их                    $result_2 = mysql_query('SELECT * FROM `users` WHERE `uid`='.$uid.';');                        if (!$result_2){                            exit('{"error":"1","desc":"mysql error '.mysql_error().'}');                        }else{                            //Выводим обновлённые данные [код из акта 2]                            $data = mysql_fetch_assoc($result);     //mysql_fetch_assoc потому что нам надо обращаться к переменныем таким способом : $data['money'];                            //Создаём переменные которые надо вывести :                             $money = $data['money'];                            $rait  = $data['rait'];                            $hash  = md5(md5($money).md5($rait));                            //Выводим результат                            exit('{"error":"0","money":"'.$money.'","rait":"'.$rait.'","hash":"'.$hash.'"}');                        }                    }                }else{                    //Попытка обмана                    exit('{"error":"1","desc":"invalid hash"}');                }            break;            default:            //Неизвестный акт            exit('{"error":"1","desc":"unknown act"}');            break;        }    }else{    //Ошибка в key (скорее всего подмена данных    exit('{"error":"1","desc":"key invalid"}');    }}else{//Ошибка в auth_keyexit('{"error":"1","desc":"auth key invalid"}');}?> 
Просмотрите этот код внимательно, теперь мы приступаем к клиенту.
[шаг 4: пишем клиент]
Я не буду описывать весь процесс создания приложения поэтому приведу ниже код и расскажу о некоторых особенностях.

Код: Выделить всё

 Файл [b]ServerAPI.as[/b] :package  {    import flash.net.*;    import flash.events.*;    import vk.api.MD5;    import vk.api.serialization.json.JSON;        public class ServerAPI {            private var server:String = null;            private var uid:String = null;            private var auth:String = null;                    public function ServerAPI(parms:Object) {            server = parms.url;            uid    = parms.uid;            auth   = parms.auth;        }        public function Executure(parms:Object){            /*                Код представленный ниже взят из основы написанной в данной теме : http://flapps.ru/forum/topic165.html            */            var core_loader:URLLoader = new URLLoader();            var core_request:URLRequest=new URLRequest(server + "core.php");            core_request.method=URLRequestMethod.POST;            switch (parms.act)            {                case "AUTH_USR":                    core_vars['viewer_id'] = uid;                    core_vars['auth_key'] = auth;                    core_vars['act'] = parms.act;                    core_vars['key'] = MD5.encrypt(MD5.encrypt(parms.act) + MD5.encrypt(auth) + MD5.encrypt(uid));                    //Больше параметров нет                break;                case "GET_USER_INFO":                    core_vars['viewer_id'] = uid;                    core_vars['auth_key'] = auth;                    core_vars['act'] = parms.act;                    core_vars['key'] = MD5.encrypt(MD5.encrypt(parms.act) + MD5.encrypt(auth) + MD5.encrypt(uid));                    //Больше параметров нет                break;                case "UPDATE_USER_INFO":                    core_vars['viewer_id'] = uid;                    core_vars['auth_key'] = auth;                    core_vars['act'] = parms.act;                    core_vars['key'] = MD5.encrypt(MD5.encrypt(parms.act) + MD5.encrypt(auth) + MD5.encrypt(uid));                    //Прочие параметры                    core_vars['m_add'] = parms.money;                    core_vars['r_add'] = parms.rait;                    core_vars['c_add'] = MD5.encrypt(parms.money + auth + parms.rait);                break;                default:                    throw new Error("Act is not valid");                break;            }            var core_vars:URLVariables = new URLVariables();            core_request.data=core_vars;            core_loader.addEventListener(Event.COMPLETE, function(e:Event){                trace(e.target.data);       //Для отладки                var data:Object = JSON.decode(e.target.data);                if (data['error'] == '0'){                    //Ошибок нет                    parms.onComplete(data);                }else{                    parms.onError(data);                }            });            core_loader.addEventListener(IOErrorEvent.IO_ERROR, function(e:Event){                //Если ошибка из - за отсуствия доступа к скрипту                parms.onError({error:1,desc:"IO_SCRIPT_ERROR"});        //Даём понять что ошибка не в скрипте            });            core_loader.load(core_request);        }    }} 
Особенности :
  • 1. Только 1 аргумент во всех функциях (включая конструктор)
    2. Обработка всех основных событий
    3. Дополняемость класса (вы можете дописать класс или переделать его)
Представленный выше код это класс помогающий работать с нашей серверной частью. Вот пример его использования (авторизация юзера) :

Код: Выделить всё

             var net:ServerAPI = new ServerAPI({url:"http://myserver.ru/",uid:"ваш ID",auth:"ваш auth key"});            net.Executure({onComplete: function(data:Object){                //Успешный запрос              }, onError: function(data:Object){                //Ошибка            } }); 
Ну вот и всё. Для тех кто знает азы и изучал уроки на этом форуме будет не сложно разобраться с кодом.
От себя : Поздравляю всех с днём знаний ;)
P.S. В данном уроке я не рассмотрел проверку времени последнего захода (защита от частых заходов). Это будет ваша домашняя работа :ugeek:
gpv123
Сообщения: 346
Зарегистрирован: 29 янв 2012, 20:57

Re: Создаём универсальный класс взаимодействия с вашим сервером

Сообщение gpv123 »

AlexProger писал(а):Данный акт требует дополнительные данные (кол-во добавляемых едениц) и нам надо защитить эти данные. Поэтому мы будем принимать к этому методу не 2 параметра, а 3.
Параметры :
1. money - это сколько надо добавить денег
2. rait - это сколько надо добавить рейтинга
3. checker - это хэш данных (защищает только money и rait и привязан к auth_key)
Формула составления хэша :
md5(money + auth + rait);
Это все можно подменить.
AlexProger писал(а):

Код: Выделить всё

if (md5(md5($act).md5($auth).md5($uid)) == $key)
Ну зачем? В чем смысл? $uid и $auth уже проверены чуть выше, а $act проверять нет смысла.
AlexProger
Сообщения: 6
Зарегистрирован: 01 сен 2012, 16:42

Re: Создаём универсальный класс взаимодействия с вашим сервером

Сообщение AlexProger »

Это все можно подменить.

if (md5(md5($act).md5($auth).md5($uid)) == $key)

Ну зачем? В чем смысл? $uid и $auth уже проверены чуть выше, а $act проверять нет смысла.
Если знаешь как этот хэш составлялся то можно подменить. Я не призываю использовать именно мой код, я лишь показал как это можно реализовать. :)
Ну зачем? В чем смысл? $uid и $auth уже проверены чуть выше, а $act проверять нет смысла.
Смысл проверять есть всегда! :!:

Тем более что представленный код это отформатированные отрывки кода из моего проекта. Хотел поделиться с людьми ;)
gpv123
Сообщения: 346
Зарегистрирован: 29 янв 2012, 20:57

Re: Создаём универсальный класс взаимодействия с вашим сервером

Сообщение gpv123 »

AlexProger писал(а):Если знаешь как этот хэш составлялся то можно подменить.
Это можно узнать при декомпиляции флешки. Наверняка найдется тот, кто не пожалеет времени и взломает приложение.
AlexProger
Сообщения: 6
Зарегистрирован: 01 сен 2012, 16:42

Re: Создаём универсальный класс взаимодействия с вашим сервером

Сообщение AlexProger »

Это можно узнать при декомпиляции флешки. Наверняка найдется тот, кто не пожалеет времени и взломает приложение.
Если обфуцировать флешку то это будет сделать не так легко (я тестировал на своей флешке тремя декомпиляторами и не один из них не выдал правильный код) Может об этом тоже напишу статейку ;)
gpv123
Сообщения: 346
Зарегистрирован: 29 янв 2012, 20:57

Re: Создаём универсальный класс взаимодействия с вашим сервером

Сообщение gpv123 »

AlexProger писал(а):Если обфуцировать флешку то это будет сделать не так легко
Можно попробовать, но гарантировать, что ее не взломают, нельзя.
Во всяком случае пользователь может во время передачи данных посмотреть на поля money и rait, понять, что все происходит в клиенте и начать ломать вообще вручную.
Но есть вариант еще легче: через артмани можно подменить переменную внутри работающей программы, после чего прога сама отправит неправильные данные, но с подходящим хешем.

По теме: http://flapps.ru/forum/topic5391.html
AlexProger
Сообщения: 6
Зарегистрирован: 01 сен 2012, 16:42

Re: Создаём универсальный класс взаимодействия с вашим сервером

Сообщение AlexProger »

Но есть вариант еще легче: через артмани можно подменить переменную внутри работающей программы, после чего прога сама отправит неправильные данные, но с подходящим хешем.
Ну я не поднимал вопрос о безопасности. Я хотел показать как решить такую задачу т.к. некоторым будет полезно.
P.S. спс за ссылку на тему, почитаю на досуге. :mrgreen:
Ответить