пятница, 9 июля 2010 г.

Сквозная аутенфикация при помощи JA-SIG CAS. Часть вторая - реализуем обработчик аутенфикации.

В первой части статьи было описано, каким образом развернуть и настроить систему для сборки CAS-сервера. В реализации по-умолчанию, CAS сервер допускает только тех пользователей, чьи логин и пароль совпадают. Во второй статье хотелось бы остановиться на  вопросе создания собственного обработчика аутенфикации. В качестве примера показана привязка аутенфикационных данных учетной записи пользователя к записи в базе данных почтового сервера postfix.
CAS поддерживает достаточно большое количество различных схем аутенфикации, среди которых и LDAP, и OpenId, Radius, X.509. В моем случае, учетные записи пользователей хранились в базе данных почтового сервера корпоративной почты на базе postfix. Такой подход позволяет при создании почтового ящика, например для нового пользователя, автоматически предоставлять ему доступ к существующим сервисам. Postfix позволяет хранить информацию о пользователях в базе данных, в частности при помощи СУБД PostgreSQL.
Напомню, что центральным модулем, на которое будет направлено наше внимание, является cas-server-webapp. Именно в нем будут сосредоточены наши изменения.
Открываем в IDE данный модуль. Прежде всего необходимо определиться с зависимостями  для cas-server-webapp. Так как в нашем случае аутенфикационные данные будут запрашиваться из базы данных, то нам потребуется указать зависимости от модуля cas-server-support-jdbc, какой-либо библиотеки, реализующей DataSource, и драйвера нашей СУБД. В моем случае я использовал commons-dbcp и postgresql.
В итоге в раздел dependencies файла pom.xml модуля cas-server-webapp  были добавлены следующие строки:

  org.jasig.cas
  cas-server-support-jdbc
  3.4.2


  postgresql
  postgresql
  8.4-701.jdbc4


  commons-dbcp
  commons-dbcp
  1.4

Перейдем к определению обработчика аутенфикации. Основные настройки процесса аутенфикации находятся в файле cas-server-webapp/src/main/webapp/WEB-INF/deployerConfigContext.xml. Данный файл является файлом контекста фреймворка spring (cas-server-webapp реализован с использованием spring framework 3). Ключевым объектом в данном файле является authenticationManager, в рамках которого определен список обработчиков аутенфикации. Если обратить внимание, то мы увидим, что в качестве обработчика аутенфикации используется SimpleTextUsernamePasswordAuthenticationHandler (это тот, который пропускает пользователей с паролем совпадающим с логином). Вот его то и предстоит заменить на нужную реализацию. 
Обобщение процесса обработки аутенфикационных данных представлено в виде интерфейса org.jasig.authentication.handler.AuthenticationHandler. Данный интерфейс лежит на самом верху глубокой иерархии абстрактных и конкретных классов, реализующих различные стратегии аутенфикации. Модуль cas-server-support-jdbc содержит набор готовых классов, поддерживающих аутенфикацию с использованием баз данных. В большинстве случаев подойдет org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler. Для настройки данного обработчика надо передать ссылку на DataSource базы данных и шаблон sql-запроса для получения пароля по имени пользователя.
Вот простой пример, настройки схемы аутенфикации с использованием данного класса. Первое - в файл deployerConfigContext.xml довляем bean описывающий DataSource:

  
  
  
  
Второй шаг - замена класса с SimpleTestUsernamePasswordAuthenticationHandler на QueryDatabaseAuthenticationHandler с указанием его параметров. Это выглядит следующим образом:

  
    
    
    
    
      
      
    
  


В том случае, если в вашей базе данных пароли пользователей хранятся в закрытом виде, то необходимо указать для QueryDatabaseAuthenticationHandler дополнительное свойство - ссылку на класс passwordEncoder (интерфейс PasswordEncoder), в котором определяется механизм закрытия пароля. К сожалению, логика класса QueryDatabaseAuthenticationHandler не подразумевает возможности использования паролей, закрытых при помощи алгоритма хеширования с маркером (salt).  А в моем случае  сервер postfix использовал именно такой метод закрытия паролей. Таким образом, без собственной реализации  AuthenticationHanlder-а было не обойтись.
Создание собственного обработчика аутенфикации не представляет особой сложности. В моем случае в первую очередь необходимо было установить, какой алгоритм использовался для закрытия паролей. При помощи openssl удалось установить, что postfix использует стандартный юниксовый алгоритм crypt на базе md5. На koders.com в течении 2 минут можно найти реализацию данного алгоритма. Далее необходимо реализовать интерфейс AuthenticationHandler. К счастью, модуль cas-server-support-jdbc содержит абстрактный класс AbstractJdbcUsernamePasswordAuthenticationHandler. Все что нам необходимо сделать, это доопределить абстрактный метод authenticateUsernamePasswordInternal. Вот моя реализация:
package insane.code.monkeys.cas.adaptors;

import ...

public class PostfixDatabaseAuthenticationHnalder extends AbstractJdbcUsernamePasswordAuthenticationHandler {
  
  private static final String POSTFIX_MAILBOX_QUERY = "select password from mailbox where username = ?";
  
  protected boolean authenticateUsernamePasswordInternal(UsernamePasswordCredentials credentials) throws AuthenticationException {
    final String username = getPrincipalNameTransformer().transform(credentials.getUsername());
    final String password = credentials.getPassword();

    try {
      final String dbPassword = getJdbcTemplate().queryForObject(POSTFIX_MAILBOX_QUERY, String.class, username);
      if ( dbPassword.length() != 34 ) return false;
      String saltPart = dbPassword.substring(3,11);
      String hashedPassword = MD5Crypt(password, saltPart);
      return hashedPassword.equals(dbPassword);
    } catch(IncorrectResultSizeDataAccessException e) {
      // это означает, что пользователя в базе нет
      return false;
    }
  }
}
Как мы можем видеть, абстрактный класс предоставляет все необходимое для комфортной работы, в частности jdbcTemplate от spring-framework.
Данный класс можно добавить непосредственно в директорию исходников модуля cas-server-webapp/src/main/java/.
Остается только убедиться в работоспособности нашего аутенфикационного обработчика, запустив jetty, открыв страницу аутенфикации и введя корректный логин и пароль. Не забудьте проверить на ложном наборе логина и пароля, дабы избежать сюрпризов в дальнейшем.
В случае возникновения ошибок в аутенфикации, всегда можно дополнить аутенфикационный обработчик трассировочными сообщениями.
Не составляет труда понять, что реализация других механизмов аутенфикации проходит по аналогичному сценарию. В модуле cas-server-webapp прописывается соответствующая зависимость cas-server-support-, и в файле deployerConfigContext.xml настраивается authenticationManager путем указания того или иного authenticationHandler (идут в поставке каждого модуля). Так же не должно составить труда реализовать при необходимости собственный модуль аутенфикации, например, получающий аутенфикационные данные из файла.

Комментариев нет: