Самое не приятное, с чем можно встретиться на многих сайтах — это регистрация учетной записи. Неприятное ощущение усиливается ещё и от контраста — везде написано — как мы рады вас видеть, подписываетесь, голосуйте, покупайте и т.п. И вот вы решаете как то поучаствовать в проекте и жмете кнопку «регистрация». Вот тут, обычно, для пользователя начинается самое не приятное.
Глазами пользователя
Во-первых, вам покажут, что хотят убедится, что вы не спамер — подсунут кептчу. Во-вторых, авторы проверят вашу почту (есть она или нет на самом деле) — вышлют туда активационный код, и нужно будет активировать учетку. Кроме этих унизительных процедур, вам предложат придумать какой то логин, которого на сайте ещё не было, придумать или «засветить» свой пароль, и заполнить какие то ещё, бог знает кому нужные, поля.
За это время можно расхотеть «покупать», комментировать и вообще смотреть на этот сайт. Тем более, что у вас есть куча учеток в соц. сетях (СС), и заводить ещё одну новую на каком то «левом» сайте вы не собирались.
Глазами программиста
Программистов тоже можно понять. Во-первых, спамеры, во-вторых, сеошники. Лезут во все дыры сайта, суют свои поганые ссылки на «энладж ё пенис» и «чиап лоан». Вот и вырастают заборы из кептч, проверок, анкет….
И на эти заборы кроме спамеров натыкаются и те, кто, возможно, собирается что то купить на сайте, а терпения на форму регистрации у них уже может не хватить.
Глазами OPEN-ID
Казалось бы давно решенная проблема. Есть же авторизация/регистрация по OpenID. В drupal встроена в ядро. Но пользователь, который только что вылез из ВКонтакте, скорее всего даже слов таких не слышал и пройдет мимо данной возможности.
Loginza.ru
И вот специально для таких «друзей» есть openid провайдер loginza.ru. Кроме собственно самой учетки стандарта Open ID 2.0, сервис интегрирован в соц.сети и может от туда передать данные пользователя. Т.е. можно получить данные учетной записи из любимой юзером СС., а процесс регистрация-проверка-авторизация сведется к нескольким кликам мыши.
Глазами Drupal
На логинзе есть какой то сторонний вариант-заготовка для drupal. Можете попробовать его. Впрочем, я не получил того, что мне было нужно. Давно известно, что «хочешь сделать что то хорошо, сделай это сам».
Итак, задача. Пользователь по данным из соц. сети должен получить учетку в drupal. Если значение поля email передаётся из СС, и оно уже есть в базе — авторизуемся под учеткой данного пользователя (очень удобно, но довольно небезопасно!). Если данных о email нет или в базе нет такой почты — пытаемся регистрировать нового пользователя. Все это будем решать для Drupal версии 6. (Для 7-й мне кажется даже переделывать ничего не придется, не проверял).
При регистрации drupal требует 2 вещи — придумать логин и сообщить e-mail. Данные, полученные из СС, могут не содержать ни того ни другого. Единственно, что «железно» сообщается — это уникальная ссылка на профиль пользователя в этой СС.
Регистрируя пользователя «изнутри» можно обойтись и без e-mail, т.к. база данных не требует от этого поля (users.mail) быть уникальным. Важно только сгенерировать уникальное имя пользователя (users.name).
Далее подробно по шагам…
Готовим почву на loginza.ru
Здесь нужно создать учетку. В аккаунте добавьте пару «виджетов» для вашего сайта. Логинза отличает сайт с www и без — потому придется делать два.
Нужно будет подтвердить права на сайт, и дальше перейдите к настройкам.
Тут следует обратить внимание на галку — «Безопасный режим…». Это наш вариант. А коды (ID и skey) будут использованы далее в программе.
Код виджета
С логинзы надо взять образец HTML кода для вставки на ваш сайт. Есть несколько вариантов, все они содержат в своих параметрах обращение к api логинзы с запросом вида
1 |
.. u/api/widget?token_url=http://www.ваш-сайт.домен.. |
Стоит поменять переменную token_url на какую то более универсальную запись (мы тут пишем, напоминаю, на php для drupal):
1 |
.. ?token_url=http://<?=$_SERVER['HTTP_HOST']?>/user_profile .. |
так мы учтем оба варианта — с www и без оного. А страницу /user_profile мы запрограммируем для анализа данных отправляемых сайту с loginza.ru
Модуль в drupal
Вы можете создать новый модуль для друпал или использовать существующий для размещения следующих hook-ов и объявлений. Пусть модуль называется «loginza» для определенности.
Создаём раздел сайта — вход для обработки данных авторизации
1 2 3 4 5 6 7 8 9 10 11 12 |
function loginza_menu() { $items = array(); /* LOGINZA.RU */ $items['user_profile'] = array( 'title' => 'Войти на сайт, используя учетные записи на других сайтах.', 'page callback' => '_loginza_page', 'access arguments' => array('access content'), 'type' => MENU_NORMAL_ITEM ); return $items; } |
далее сам обработчик.
1 2 3 4 5 6 7 8 9 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 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
/* обработка данных авторизации через логинзу-ру */ function _loginza_page() { global $user; if ($user->uid) { //уже авторизирован drupal_goto('user/' . $user->uid); } else { if (!empty($_POST['token'])) { //два ключа - варианты с www и без него switch (strtolower($_SERVER['HTTP_HOST'])) { case 'ваш-сайт.домен': $skey = '...'; //секретный ключ $id = '..'; //ID break; case 'www.ваш-сайт.домен': $skey = '...'; //секретный ключ для сайта с www $id = '..'; //ID для сайта с www } //проверка, что данные пришли с логинзы $sig = md5($_POST['token'] . $skey); $responde = file_get_contents("http://loginza.ru/api/authinfo" . "?token={$_POST['token']}&id=$id&sig=$sig"); $result = json_decode($responde); // print_r($result); - для тестовых ситуации можно "раскоментить" :) if (!empty($result->error_type)) { //не удачно прошла авторизация - как раз тестовая ситуация :) return '<p>Авторизация не была завершена или произошла ошибка!</p>'; } else { //авторизация с использованием email //(часть кода заимствована из ядра drupal) if (!empty($result->email) && valid_email_address($result->email)) { //проверим e-mail по базе $res = db_query("SELECT * FROM users WHERE mail like '%s'", $result->email); if (mysql_numrows($res)) { //есть такой юзер - авторизация под ним $account = user_load(array('mail' => $result->email, 'status' => 1)); if ($account) { if (drupal_is_denied('mail', $account->mail)) { drupal_set_message(t('The name %name is registered using a reserved e-mail address and therefore could not be logged in.', array('%name' => $account->name))); } else { $user = $account; user_authenticate_finalize($form_values); //идем на "главную" после авторизации drupal_goto('<front>'); } } else { drupal_set_message('Учетная запись данного пользователя заблокирована!'); return '<p>Так как учетная запись заблокирована, то авторизация с её использованием невозможна.</p>'; } } } //авторизация с использованием внешнего идентификатора //проверим identity по базе $idn = substr($result->identity, 7); // отрежем http:// $res = db_query("SELECT * FROM users WHERE `identity` like '%s'", $idn); if (mysql_numrows($res)) { //есть такой юзер - авторизация под ним $account = user_load(array('identity' => $idn, 'status' => 1)); if ($account) { if (drupal_is_denied('mail', $account->mail)) { drupal_set_message(t('The name %name is registered using a reserved e-mail address and therefore could not be logged in.', array('%name' => $account->name))); } else { $user = $account; user_authenticate_finalize($form_values); //идем на "главную" после авторизации drupal_goto('<front>'); } } else { drupal_set_message('Учетная запись данного пользователя заблокирована!'); return '<p>Так как учетная запись заблокирована, то авторизация с её использованием невозможна.</p>'; } } else { //в прочих случаях проводим регистрацию пользователя $userinfo = array( 'identity' => $idn, 'mail' => $result->email, 'name' => $result->name->first_name . ' ' . $result->name->last_name, /* генерируем пароль, пользователь его вряд ли будет менять когда-либо, потому лучше сделать его качественным */ 'pass' => myLIB::pwd_generator(), 'init' => $result->name->first_name . ' ' . $result->name->last_name, 'status' => 1, 'access' => time() ); $account = user_save('', $userinfo); // Terminate if an error occured during user_save(). if (!$account) { drupal_set_message(t("Error saving user account."), 'error'); return '<p>Не удалось создать уникальной учетной записи по данным ' . $result->provider . '!</p>'; } $user = $account; //идем на "главную" drupal_goto('<front>'); } } } else { return ' <script src="http://loginza.ru/js/widget.js" type="text/javascript"></script> <iframe src="http://loginza.ru/api/widget?overlay=loginza&token_url=http://' . $_SERVER['HTTP_HOST'] . '/user_profile" style="width:359px;height:300px;" scrolling="no" frameborder="no"></iframe>'; } } } |
Небольшой кусочек сервисной библиотеки. Тут только ф-ция генерации пароля.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class myLIB { static function pwd_generator($length = 11) { //объявим словари $ac[0] = 'bcdfghjkmnpqrstvwxyz'; $ac[1] = 'aeiou'; $ac[2] = '123456789'; //выберем цифирку для затравки $pass = $ac[2][mt_rand(0, 8)]; $length--; //для признака четности (чтобы выбрать нужный словарь) $n = 0; //пока не достигнем нужной длины, будем наращивать пароль while ($length > 0) { $dict = $ac[$n++ % 2]; $d_len = strlen($dict) - 1; $pass = $dict[mt_rand(0, $d_len)] . $pass . $dict[mt_rand(0, $d_len)]; $length -= 2; } //пароль готов! return $pass; } } |
Последний штрих — нужно добавить поле identity в табличку users. Там будет сохранятся единственный железно передаваемый параметр из loginza.ru. Это поможет нам вторично пустить пользователя в эту же учетку. Можно было бы сохранять эти данные в поле users.data. Но проверять по сериализованному значению не очень удобно, да и вообще не понятно, что там может сохранять сам друпал.
Так что выполните в базе команду вида —
1 |
ALTER TABLE `users` ADD `identity` VARCHAR( 200 ) NOT NULL |
Узкие места
Еще раз опишу узкие места. Они не являются неустранимыми, обращаю на них ваше внимание.
Не уникальность имени. В данном варианте код не может зарегистрировать двух пользователей с одинаковым именем. Это не удобно. Будет выдано соответствующее сообщение.
Решение: Можно придумать какую то схему уникализации.
Авторизация по существующему в базе email. Программа доверяет данным из СС, и считает, что e-mail проверен на принадлежность пользователю. Это не всегда может быть так. Тогда злоумышленник, зная email админа, к примеру, регистрируется в какой то CC. Добивается там нужного значения email — и дело сделано, мы имеем взлом аккаунта.
Решение: Тут простой путь только один — проводить проверку e-mail, т.е. высылать проверочный код, а потом уже актуализировать авторизацию. Эх, а как не хочется напрягать юзеров лишний раз что то делать!