среда, 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.

четверг, 2 августа 2007 г.

Правильная установка perl модулей в Slackware

Способ описанный в статье «Установка perl модулей» прекрасно работает. Но пользоваться им, значит идти в обход пакетной системы дистрибутива, что влечет следующие недостатки:

  1. Невозможность удалить perl модуль. CPAN.pm только обещает эти возможности в будущем.
  2. Некорректное обновление модулей. При установке новой версии модуля поверх старой, если должны быть удалены некоторые файлы из старой версии, этого сделано не будет. В лучшем случае это означает мусор в файловой системе, в худшем различные неприятные побочные эффекты.
  3. Невозможность получить список уже установленных модулей.
  4. Замусоривание файловой системы, невозможность определить какой файл к какой программе относится.

Предварительная сборка tgz пакетов Slackware позволяет устранить все вышеперечисленные недостатки. Кроме того, сборку пакетов можно производить не на рабочем сервере, а на отдельной машине, например виртуальной. Что в свою очередь даёт преимущества:

  1. Отсутствует необходимость в средствах сборки на рабочем сервере: make, компилятор, линковщик и т.д. Это экономит место на диске сервера и делает его более безопасным.
  2. Экономия времени, если рабочих серверов больше одного. Однажды собранные пакеты можно установить на много серверов. Установка готового пакета занимает секунды, сборка модуля длится гораздо дольше.

Переходим к практической части. Вначале необходимо сконфигурировать утилиту cpan, как написано в уже упомянутой статье «Установка perl модулей». Далее, для автоматизации процесса сборки Slackware пакетов из perl модулей нам поможет замечательная утилита cpan2tgz. С её помощью можно по имени модуля собрать Slackware пакет, а также пакеты всех отсутствующих зависимых модулей. На сайте автора утилиты cpan2tgz выбираем пакет под вашу версию Slackware, скачиваем и устанавливаем его:

root@slack12:~# wget http://software.jaos.org/slackpacks/12.0/cpan2tgz-0.6.2-norch-1.tgz
...
root@slack12:~# installpkg cpan2tgz-0.6.2-noarch-1.tgz
Installing package cpan2tgz-0.6.2-noarch-1... 
PACKAGE DESCRIPTION:
cpan2tgz: cpan2tgz - create Slackware packages from CPAN Perl modules
cpan2tgz: 
cpan2tgz: Packaged by cpan2tgz
cpan2tgz: 
cpan2tgz: cpan2tgz by Jason Woodward 
cpan2tgz: 
cpan2tgz: http://software.jaos.org/
cpan2tgz: 
Executing install script for cpan2tgz-0.6.2-noarch-1...

Чтобы получить пакеты, например, runtime части web фремворка Catalyst можем воспользоватся командой:

root@slack12:~# cpan2tgz Catalyst

Начнется сборка пакетов, при этом большую часть времени работает утилита cpan, скачивая, распаковывая и устанавливая perl модули. После установки модуля cpan2tgz запаковывает его в tgz пакет с соблюдением стандартов. По умолчанию готовые пакеты помещаются в директорию /usr/src . В данном случае список состоит из:

perl-catalyst-runtime-5.7007-noarch-1.tgz
perl-cgi-simple-1.1-noarch-1.tgz
perl-class-accessor-0.31-noarch-1.tgz
perl-class-data-inheritable-0.06-noarch-1.tgz
perl-class-inspector-1.16-noarch-1.tgz
perl-compress-raw-zlib-2.005-i486-1.tgz
perl-compress-zlib-2.005-noarch-1.tgz
perl-data-dump-1.08-noarch-1.tgz
perl-file-modified-0.07-noarch-1.tgz
perl-html-parser-3.56-i486-1.tgz
perl-html-tagset-3.10-noarch-1.tgz
perl-http-body-0.9-noarch-1.tgz
perl-http-request-ascgi-0.5-noarch-1.tgz
perl-io-compress-base-2.005-noarch-1.tgz
perl-io-compress-zlib-2.005-noarch-1.tgz
perl-libwww-perl-5.806-noarch-1.tgz
perl-module-pluggable-3.6-noarch-1.tgz
perl-path-class-0.16-noarch-1.tgz
perl-sub-uplevel-0.14-noarch-1.tgz
perl-test-exception-0.25-noarch-1.tgz
perl-text-simpletable-0.03-noarch-1.tgz
perl-tree-simple-1.17-noarch-1.tgz
perl-tree-simple-visitorfactory-0.10-noarch-1.tgz
perl-version-0.7203-i486-1.tgz
perl-yaml-0.65-noarch-1.tgz

Теперь можно скопировать эти пакеты на рабочий сервер и установить c помощью команды:

root@slack12:~# installpkg *.tgz

Всё.