2011-01-18 17:16:34

Кроссдоменная авторизация

Про работу JavaScript PHP

Суть новомодной тенденции, которая сейчас набирает обороты такова: вы заходите на сайт и авторизуетесь на нем, потом переходите на другой из тойже сети, но на другом домене, и там вы автоматически являетесь авторизованными. Примером такой схемы сейчас успешно служат Яндекс, Мейл.ру и другие.

А вот что касается реализации, то тут есть масса мнений и способов, достаточно спросить гугля по фразе "кроссдоменная авторизация" и почитать длинные дискуссии. Для себя я вижу 2 варианта реализации этой моды: одно простое и красивое, но с ограничениями, другое более сложное и не такое изящное, но более свободное.

О спецификации Cross-Origin Resource Sharing речь пока не идет, поскольку она еще нигде не поддерживается.

Начнем с простого и красивого решения.


Одна сессия для нескольких доменов

Изначально предполагается, что у нас есть один домен второго уровня и несколько третьего. Например cookie.local с алиасом www.cookie.local, subd1.cookie.local, subd2.cookie.local. Авторизация пользователей производится на одной базе. Все домены, на которых возможна работа внутри домена второго уровня, т.е. соответствуют Same origin policy.

Для того, чтобы сделать сессию единой для всех доменов, нам достаточно положить ее ID в куки, которая будет прочитана со всех доменов (типа глобальная куки), вот например так:

function setGlobalSession()
{
$cookie_host = explode('.', $_SERVER['HTTP_HOST']);
if(sizeof($cookie_host) > 2) $cookie_host = array_slice($cookie_host, -2);
$cookie_host = '.'.implode('.', $cookie_host);

$cookie_params = session_get_cookie_params();
return setcookie(session_name(), session_id(), $cookie_params['lifetime'] ? time() + $cookie_params['lifetime'] : 0, $cookie_params['path'], $cookie_host, $cookie_params['secure'], $cookie_params['httponly']);
}

Опять оговорюсь, что в этом примере речь идет о домене второго уровня и поддоменах. Здесь используются session_get_cookie_params() для получения информации о параметрах сохранения cookie сессии и setcookie() чтоб установить cookie для всех доменов. Вся фишка в параметре domain у setcookie().

Для проверки функционала делаем следующее: функцию setGlobalSession() размещаем в gs.php, а в document_root каждого домена кладем вот такой index.php

<?php
require("../gs.php");

session_start();

echo setGlobalSession(); echo "<hr>";

echo "<h1>I am <u>{$_SERVER['HTTP_HOST']}</u></h1>";
echo session_name() . " = " . session_id(); echo "<hr>";
if(!isset($_SESSION['started_from'])) $_SESSION['started_from'] = $_SERVER['HTTP_HOST'];
print_r($_SESSION); echo "<hr>";
?>

<a href="http://cookie.local">host</a> <a href="http://www.cookie.local">www</a> <a href="http://subd1.cookie.local">sub1</a> <a href="http://subd2.cookie.local">sub2</a>

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

Возможная засада: неизвестно (не проверялось) как оно будет вести себя в случае, если часть сайтов работают через SSL (https).

От простого идем к сложному - наши домены не соответствуют Same origin policy, поэтому будем использовать...

Заменитель асинхронных запросов

Здесь все не так просто, нам понадобится выбрать один центральный сайт авторизации (СА), на котором и только на котором будет происходить авторизация пользователя в сессии. Со всех остальных сайтов форма ввода логина и пароля будет вести на СА, который после обработки будет редиректить обратно (некрасиво, зато практично) с указанием ID своей сессии (AUTH_SSID), в которой авторизован пользователь. AUTH_SSID своей сессии СА укладывает в базу и, получив его, остальные сайты обновляют время активности в базе, чтоб ее не зачистило. Для большей безопасности можно ввести еще и проверку IP-адреса.

В общем механизм авторизации на СА мало чем отличается от обычного, и происходит по следующей схеме:

Отличие заключается в том, что после проверки логина и пароля происходит редирект на сайт-отправитель логина и пароля с указанием AUTH_SSID для возможности пользоваться авторизацией внутри своей сессии, но об этом чуть позже.

На всех сотальных сайтах работает следующий механизм:

Первым делом проверяем наличие AUTH_SSID в своей сессии, если он есть то идем в базу с ним и все как обычно, а вот если его нет то, запускаем PHP/JavaScript прослойку с СА, которая скажет авторизованы мы или нет. Таким образом на сайте s1.local добавляем script src="CA.local/auth.js.php". Эта прослойка стартует сессию на СА, ищет в базе живую сессию со своим session_id (SS_ID) и, если она есть, отдает ее в виде переменной JavaScript, а если нет - то флаг об ошибке. Далее JavaScript на s1.local смотрит наличие AUTH_SSID (полученного из прослойки) и, если он есть, то перегружает страницу с парамтером AUTH_SSID. При загрузке страницы проверяется наличие AUTH_SSID в GET и, если есть, то записывается в сессию и все происходит начиная с "первым делом".

В общем схема авторизации на сайтах выглядит так:

Основным отличием моей идеи от конкурирующих решений является то, что я передаю идентификатор сессии в которой авторизован пользователь, а не пытаюсь стартовать сессию с тем же ID, это позволяет использовать несколько физических серверов вместо одного.

Основным недостатком является необходимость перегружать страницу при первом посещении.

Основной вопрос для размышления - выявление дырок в безопасности...