среда, 29 августа 2007 г.

HTTP-авторизация и Catalyst

Протокол HTTP предоставляет возможность авторизации пользователя по имени и паролю. Вкратце схема работы выглядит так:

  1. Веб-браузер посылает обычный http-запрос на доступ к запороленному ресурсу на веб-сервере. Пример:

    GET / HTTP/1.1
    ...
  2. Веб-сервер или веб-приложение формируют http-ответ с кодом 401 и требованием авторизации. Пример basic авторизации:

    HTTP/1.0 401 Unauthorized
    WWW-Authenticate: Basic realm="need authorization"
    ...
  3. Веб-браузер отображает окно авторизации. Например так:

    И после ввода данных посылает второй запрос к веб-ресурсу, содержащий base64-кодированный заголовок Authorization:

    GET / HTTP/1.1
    Authorization: Basic YWRtaW46c2VjcmV0
    ...
  4. Веб-приложение проверяет правильность имени и пароля. Если проверка успешна, выдает нормальный http-ответ:

    HTTP/1.0 200 OK
    ...

    Если проверка не успешна, то повторяется http-ответ с кодом 401. При этом в теле http-ответа уже может содержаться дополнительная информация, объясняющая причину отказа.

Про http-авторизацию можно почитать в rfc 2068. Или в русском переводе этого rfc. Механизм http-авторизации не лишен недостатков. Подробно они описаны в статье «Базовая HTTP-авторизация — защита от честных людей» Алексея Мичурина, опубликованной в журнале «Системный администратор» №5 за 2005 г.

Мы рассмотрим использование http-авторизации c perl веб-фреймворком Catalyst. Для Catalyst существует плугин Catalyst::Plugin::Authentication реализующий основу любой авторизации. Но чтобы его использовать нужны ещё два плугина:

  1. Credential — метод получения информации о пользователе. Доступны такие:

    • Password — авторизация по логину и паролю полученному, например, через веб-форму.
    • HTTP — получение информации о пользователе через http-авторизацию. Именно этим плугином и воспользуемся.
    • Прочие.
  2. Store — способ хранения и проверки полученной информации. Возможные хранилища:

    • DBIC — хранение информации о пользователях в базе данных, с доступом к ней через ORM, например DBIx::Class.
    • Htpasswd — излечение информации из htpasswd файлов, создаваемых одноименной утилитой, входящей в комплект веб-сервера apache. Воспользуемся этим плугином.
    • Minimal — данные о пользователях хранятся внутри конфигурации Catalyst-проекта.
    • Прочие.

Итак, нам будут нужны следующие установленные perl-модули, естественно, со всеми зависимостями:

  • Catalyst::Devel
  • Catalyst::Plugin::Authentication
  • Catalyst::Plugin::Authentication::Credential::HTTP
  • Catalyst::Plugin::Authentication::Store::Htpasswd

Создаем заготовку проекта на фреймворке Catalyst с названием MyApp.

root@darkstar:~# catalyst.pl MyApp
created "MyApp"
created "MyApp/script"
created "MyApp/lib"
created "MyApp/root"
created "MyApp/root/static"
created "MyApp/root/static/images"
created "MyApp/t"
created "MyApp/lib/MyApp"
created "MyApp/lib/MyApp/Model"
created "MyApp/lib/MyApp/View"
created "MyApp/lib/MyApp/Controller"
created "MyApp/myapp.yml"
created "MyApp/lib/MyApp.pm"
created "MyApp/lib/MyApp/Controller/Root.pm"
created "MyApp/README"
created "MyApp/Changes"
created "MyApp/t/01app.t"
created "MyApp/t/02pod.t"
created "MyApp/t/03podcoverage.t"
created "MyApp/root/static/images/catalyst_logo.png"
created "MyApp/root/static/images/btn_120x50_built.png"
created "MyApp/root/static/images/btn_120x50_built_shadow.png"
created "MyApp/root/static/images/btn_120x50_powered.png"
created "MyApp/root/static/images/btn_120x50_powered_shadow.png"
created "MyApp/root/static/images/btn_88x31_built.png"
created "MyApp/root/static/images/btn_88x31_built_shadow.png"
created "MyApp/root/static/images/btn_88x31_powered.png"
created "MyApp/root/static/images/btn_88x31_powered_shadow.png"
created "MyApp/root/favicon.ico"
created "MyApp/Makefile.PL"
created "MyApp/script/myapp_cgi.pl"
created "MyApp/script/myapp_fastcgi.pl"
created "MyApp/script/myapp_server.pl"
created "MyApp/script/myapp_test.pl"

Чтобы убедится что всё прошло успешно, запустим проект на встроенном в Catalyst тестовом веб-сервере.

root@darkstar:~# MyApp/script/myapp_server.pl
[debug] Debug messages enabled
[debug] Loaded plugins:
.----------------------------------------------------------------------------.
| Catalyst::Plugin::ConfigLoader  0.14                                       |
| Catalyst::Plugin::Static::Simple  0.19                                     |
'----------------------------------------------------------------------------'

[debug] Loaded dispatcher "Catalyst::Dispatcher"
[debug] Loaded engine "Catalyst::Engine::HTTP"
[debug] Found home "/root/MyApp"
[debug] Loaded Config "/root/MyApp/myapp.yml"
[debug] Loaded components:
.-----------------------------------------------------------------+----------.
| Class                                                           | Type     |
+-----------------------------------------------------------------+----------+
| MyApp::Controller::Root                                         | instance |
'-----------------------------------------------------------------+----------'

[debug] Loaded Private actions:
.----------------------+--------------------------------------+--------------.
| Private              | Class                                | Method       |
+----------------------+--------------------------------------+--------------+
| /default             | MyApp::Controller::Root              | default      |
| /end                 | MyApp::Controller::Root              | end          |
'----------------------+--------------------------------------+--------------'

[info] MyApp powered by Catalyst 5.7007
You can connect to your server at http://darkstar:3000

Как видно, всё замечательно работает. Теперь мы можем обращаться на 3000 порт тестового сервера из браузера и видеть приветственную заставку по умолчанию.

Начинаем прикручивать http-авторизацию. Открываем основной модуль приложения /MyApp/lib/MyApp.pm, находим строчку, отвечающую за загрузку плугинов:

use Catalyst qw/-Debug ConfigLoader Static::Simple/;

И меняем её на:

use Catalyst qw/
    -Debug

    ConfigLoader
    Static::Simple

    Authentication
    Authentication::Store::Htpasswd
    Authentication::Credential::HTTP
/;

Добавляя таким образом загрузку необходимых плугинов авторизации. Теперь изменим файл конфигурации проекта. Открываем /MyApp/myapp.yml и вставляем следующие строчки:

---
name: MyApp

authentication:
  htpasswd: /root/MyApp/myapp.htpasswd
  http:
    type: basic

Это файл в странном формате YAML, так что соблюдение всех отступов обязательно. Зато он людьми воспринимается легче чем, например, XML. Желающие изучить вопрос могут начать с последней версии YAML спецификации :-) . Для проверки синтаксиса *.yml файла можно использовать утилиту ysh из perl модуля YAML.pm:

root@darkstar:~# cat MyApp/myapp.yml | ysh
$VAR1 = {
  'name' => 'MyApp',
  'authentication' => {
    'htpasswd' => '/root/MyApp/myapp.htpasswd',
    'http' => {
      'type' => 'basic'
    }
  }
};

Если проверка завершается успешно, то на выходе видим соответствующую perl структуру данных. Далее создаем htpasswd файл:

root@darkstar:~# htpasswd -c /root/MyApp/myapp.htpasswd admin
New password:
Re-type new password:
Adding password for user admin
root@darkstar:~# cat /root/MyApp/myapp.htpasswd
admin:mLT84dDiy3H7U

Теперь для авторизированного доступа к определенному action в него достаточно добавить одну строчку кода. Например для доступа ко всем action контроллера Root.pm используем специальный action — auto. Добавим следующие строчки к файлу MyApp/lib/MyApp/Controller/Root.pm:

sub auto : Private {
    my ( $self, $c ) = @_;
    $c->authorization_required( realm => 'catalyst http authorization' );
}

Всё, задача выполнена. Опять запускаем тестовый web-сервер:

root@darkstar:~# MyApp/script/myapp_server.pl
[debug] Debug messages enabled
[debug] Loaded plugins:
.----------------------------------------------------------------------------.
| Catalyst::Plugin::Authentication  0.10002                                  |
| Catalyst::Plugin::Authentication::Credential::HTTP  0.09                   |
| Catalyst::Plugin::Authentication::Store::Htpasswd  0.02                    |
| Catalyst::Plugin::ConfigLoader  0.14                                       |
| Catalyst::Plugin::Static::Simple  0.19                                     |
'----------------------------------------------------------------------------'

[debug] Loaded dispatcher "Catalyst::Dispatcher"
[debug] Loaded engine "Catalyst::Engine::HTTP"
[debug] Found home "/root/MyApp"
[debug] Loaded Config "/root/MyApp/myapp.yml"
[debug] Loaded components:
.-----------------------------------------------------------------+----------.
| Class                                                           | Type     |
+-----------------------------------------------------------------+----------+
| MyApp::Controller::Root                                         | instance |
'-----------------------------------------------------------------+----------'

[debug] Loaded Private actions:
.----------------------+--------------------------------------+--------------.
| Private              | Class                                | Method       |
+----------------------+--------------------------------------+--------------+
| /default             | MyApp::Controller::Root              | default      |
| /end                 | MyApp::Controller::Root              | end          |
| /auto                | MyApp::Controller::Root              | auto         |
'----------------------+--------------------------------------+--------------'

[info] MyApp powered by Catalyst 5.7007
You can connect to your server at http://darkstar:3000

Обратите внимание на лог обработки http-запросов:

[info] *** Request 1 (0.143/s) [5722] [Wed Aug 29 12:52:36 2007] ***
[debug] "GET" request for "/" from "172.16.197.1"
[debug] Checking http basic authentication.
[info] Request took 0.015420s (64.851/s)
.----------------------------------------------------------------+-----------.
| Action                                                         | Time      |
+----------------------------------------------------------------+-----------+
| /auto                                                          | 0.002696s |
| /end                                                           | 0.000278s |
'----------------------------------------------------------------+-----------'

[info] *** Request 2 (0.125/s) [5722] [Wed Aug 29 12:52:45 2007] ***
[debug] "GET" request for "/" from "172.16.197.1"
[debug] Checking http basic authentication.
[debug] Successfully authenticated user 'admin'.
[info] Request took 0.025378s (39.404/s)
.----------------------------------------------------------------+-----------.
| Action                                                         | Time      |
+----------------------------------------------------------------+-----------+
| /auto                                                          | 0.013852s |
| /default                                                       | 0.001557s |
| /end                                                           | 0.000250s |
'----------------------------------------------------------------+-----------'

Первый запрос «Request 1» ещё не содержит информации об авторизации. Action обработчик default так и не был выполнен. Запрос «Request 2» уже содержит заполненный http-заголовок Authorization. По этим данным успешно авторизуется пользователь admin. Управление передается action-у, отвечающему за запрошенный урл — default.

Фактически всё программирование свелось к написанию одной строчки кода. Все остальные действия это установка perl-модулей, внесение изменений в существующие конфигурационные файлы и создание новых конфигурационных файлов. Т.е. это действия системного администратора, а не программиста. Это есть одна из основных и моя самая любимая черта веб-фреймворка Catalyst.

3 комментария:

don_alessandro комментирует...

Спасибо за хорошую статью,
только не понял как сам catalyst установить
?

Установил
# apt-get install perl-Catalyst
# apt-get install perl-Catalyst-Devel
# apt-get install perl-Catalyst-Runtime

Но сам
# catalyst.pl
система не находит
:(

xeim комментирует...

Способ корректной установки сильно зависит от вашего дистрибутива linux и используемой там пакетной системы. Пока не назовете название ничего конкретно посоветовать не могу...

В крайнем случае всегда можете воспользоваться установкой perl модулей через cpan.

Анонимный комментирует...

Дистрибутив ALT Linux 4.0

Но тем не менее я воспользовался вашим советом и установил из CPAN.

Премного благодарен.
:)