Обеспечение безопасности Java-приложений с помощью Acegi: Часть 4. Защита JSF-приложений

Настраиваемая система безопасности для JavaServer Faces-приложений

Билал Сиддикви продолжает эту серию статей, показывая, как использовать Acegi для защиты Java™ Server Faces (JSF)-приложений. В ней рассказывается, как настроить JSF и Acegi для совместной работы в сервлет-контейнере, и рассматривается взаимодействие компонентов JSF и Acegi друг с другом.

Билал Сиддикви, внештатный консультант, WaxSys

Билал Сиддикви (Bilal Siddiqui) является инженером-электронщиком, консультантом по XML и соучредителем WaxSys, компании, чья деятельность направлена на упрощение электронного бизнеса. После окончания в 1995 г. Инженерно-технологического Университета, г. Лахор, и получения степени по электронной технике, он начал разрабатывать программные продукты для промышленных систем управления. В дальнейшем он занимался XML и использовал свой опыт в программировании C++ для разработки Web- и Wap-базируемых инструментов для XML-технологий, серверных парсинговых программных продуктов и служебных приложений. Билал – проповедник передовых технологий и часто публикуется в этой области.



13.10.2009

Первые три части этой серии статей знакомят с использованием инфраструктуры безопасности Acegi (Acegi Security System) для защиты корпоративных Java-приложений:

  • Первая часть показывает, как использовать встроенные фильтры Acegi для реализации простой системы безопасности, основанной на URL.
  • Вторая часть показывает, как написать политику контроля доступа, сохранить ее в сервере каталогов LDAP и настроить Acegi для взаимодействия с LDAP-сервером для реализации этой политики.
  • Третья часть показывает, как использовать Acegi для защиты доступа к экземплярам Java-классов в корпоративных приложениях.

В этой четвертой статье рассказывается, как использовать Acegi для защиты JavaServer Faces (JSF)-приложений, работающих в сервлет-контейнере. Сначала объясняется, какие возможности предоставляет Acegi для этой задачи, и развеиваются некоторые общие заблуждения об использовании Acegi и JSF. Затем в статье представлен простой файл web.xml, который можно использовать для развертывания Acegi для защиты JSF-приложения. Далее статья переходит к внутренней структуре Acegi и JSF-компонентов для понимания последовательности событий, которые происходят при установке файла web.xml и при обращении пользователя к JSF-приложению. Статья завершается примером JSF-приложения, защищенного Acegi.

Усиление безопасности без создания Java кода

Вернемся к первому в этой серии примеру Acegi-приложения (см. раздел "Простое приложение Acegi" в первой части). Это приложение использует Acegi для реализации следующих функций безопасности:

  • Выдача страницы для входа в систему, когда неаутентифицированный пользователь пытается обратиться к защищенному ресурсу.
  • Перенаправление авторизованного пользователя напрямую к затребованному защищенному ресурсу.
  • Выдача страницы "отказано в доступе" если пользователь не авторизован для доступа к защищенному ресурсу.

Важно помнить, что не требуется писать никакого Java-кода для получения этих возможностей. Необходимо только настроить Acegi. Также возможно получить эти возможности от Acegi в JSF-приложении без написания всякого Java-кода.

Развеиваем заблуждения

Некоторые авторы, по-видимому, считают, что интеграция Acegi с JSF требует, чтобы JSF-приложение предоставляло страницу для входа в систему (см. раздел Ресурсы). Это неправда. Это обязанность Acegi - выдавать страницу для входа в систему, когда она потребуется. Также Acegi гарантирует, что страница для входа в систему будет выдана только один раз в течение защищенного сеанса. Аутентифицированные и авторизованные пользователи могут запросить защищенный ресурс без повторного выполнения процедуры входа в систему.

Если использовать JSF для выдачи страницы входа в систему, то возникают две основные проблемы:

  • Не используются возможности Acegi для обслуживания страницы для входа в систему, когда она требуется. Необходимо написать Java-код для реализации всей логики, обслуживающей страницу для входа в систему.
  • Необходимо написать некоторое количество Java-кода для передачи параметров, идентифицирующих пользователя, например, имени пользователя и пароля со страницы для входа в систему JSF в Acegi.

Acegi задумана как альтернатива написанию кода, обеспечивающего безопасность в Java. Использование JSF для обслуживания страницы для входа в систему противоречит этой цели и приводит к возникновению множества проблем с интеграцией JSF и Acegi, большинство из которых связаны с фактом, что Acegi предназначена для реализации настраиваемой безопасности. Если использовать JSF для выполнения работы Acegi, могут возникнуть проблемы.

Оставшаяся часть этой статьи объясняет и демонстрирует, как можно разрабатывать JSF-приложения независимо от Acegi и в дальнейшем конфигурировать Acegi для защиты JSF-приложений без необходимости написания какого-либо Java-кода. Начнем с изучения файла web.xml, который можно развернуть для защиты JSF-приложения.


Развертывание Acegi для защиты JSF-приложения

В листинге 1 показан файл web.xml (часто называемый дескриптором развертывания - deployment descriptor), который можно использовать для развертывания Acegi с целью защиты JSF-приложений, работающих внутри сервлет-контейнера (такого как Apache Tomcat):

Листинг 1. Файл web.xml file для установки Acegi и JSF в сервлет-контейнер
<?xml version="1.0"?>
<!DOCTYPE web-app PUBLIC
  "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
  "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
    <context-param>
       <param-name>contextConfigLocation</param-name>
       <param-value>/WEB-INF/acegi-config.xml</param-value>
    </context-param>

    <context-param>
        <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
        <param-value>server</param-value>
    </context-param>

    <context-param>
        <param-name>javax.faces.CONFIG_FILES</param-name>
        <param-value>/WEB-INF/faces-config.xml</param-value>
    </context-param>

    <listener>
        <listener-class>
           org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>

    <listener>
        <listener-class>
           com.sun.faces.config.ConfigureListener
        </listener-class>
    </listener>

    <!-- сервлет Faces (основной компонент JSF-приложения) -->
    <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup> 1 </load-on-startup>
    </servlet>

    <!-- URL-шаблон, соответствующий Faces сервлету -->
    <servlet-mapping>
        <servlet-name>Faces Servlet</servlet-name>
        <url-pattern>*.faces</url-pattern>
    </servlet-mapping>


    <!-- конфигурация фильтра Acegi -->
    <filter>
        <filter-name>Acegi Filter Chain Proxy</filter-name>
        <filter-class>
            org.acegisecurity.util.FilterToBeanProxy
        </filter-class>
        <init-param>
            <param-name>targetClass</param-name>
            <param-value>
                org.acegisecurity.util.FilterChainProxy
            </param-value>
        </init-param>
     </filter>

    <!-- URL-шаблон, соответствующий фильтру Acegi -->
    <filter-mapping>
        <filter-name>Acegi Filter Chain Proxy</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

</web-app>

Важно отметить, что листинг 1 содержит следующие теги:

  • Три тега <context-param> (параметр контекста)
  • Два тега <listener> (обработчик событий)
  • Один тег <filter> (фильтр)
  • Один тег <servlet> (сервлет)
  • Один тег <servlet-mapping> (URL-шаблон для сервлета)
  • Один тег <filter-mapping> (URL-шаблон для фильтра)

Далее рассматривается назначение каждого из этих тегов в приложении JSF-Acegi.

Передача параметров контекста в Acegi и JSF

Каждый тег <context-param> в листинге 1 определяет параметр, который требуется Acegi или JSF во время запуска или работы. Первый параметр contextConfigLocation определяет местоположение конфигурационного XML файла Acegi.

Параметры javax.faces.STATE_SAVING_METHOD и javax.faces.CONFIG_FILES требуются для JSF. Параметр javax.faces.STATE_SAVING_METHOD определяет, как предполагается хранить состояние представления JSF-страницы: на клиенте или на сервере. По умолчанию эталонная реализация JSF от Sun хранит представления JSF на сервере.

Параметр javax.faces.CONFIG_FILES определяет местоположение конфигурационных файлов, требующихся для JSF. Подробная информация о конфигурационных файлах выходит за рамки этой статьи. (В разделе Ресурсы представлены ссылки на материалы по этой теме.)

Конфигурация обработчиков для Acegi и JSF

Теперь рассмотрим два тега <listener> в листинге 1. Теги <listener> определяют классы обработчиков, которые прослушивают и обрабатывают определенные события, возникающие во время запуска или исполнения JSP или сервлет-приложения. Параметры тега могут включать обработчики следующих событий:

  • Сервлет-контейнер создает новый контекст сервлета (servlet context) при запуске JSP или сервлет-приложения. Это событие возникает каждый раз, когда запускается JSP или сервлет-приложение.
  • Сервлет-контейнер создает новый объект запроса для сервлета (servlet request). Это событие случается каждый раз, когда контейнер получает HTTP-запрос от клиента.
  • Устанавливается новый HTTP-сеанс с пользователем. Это событие происходит, когда запрашивающий клиент устанавливает HTTP-сеанс с сервлет-контейнером.
  • Добавляется новый атрибут в объекты контекста сервлета, запроса сервлета или HTTP-сеанса.
  • Изменяется или удаляется существующий атрибут из объектов контекста сервлета, запроса сервлета или HTTP-сеанса.

Тег <listener> - это механизм расширения, позволяющий приложению, работающему внутри сервлет-контейнера выполнять обработку в связи с определенными событиями. В спецификации сервлетов определены несколько интерфейсов, которые должны реализовывать классы-обработчики для обработки событий.

Например, в инфраструктуре Spring Framework реализуется интерфейс обработки событий для контекста сервлета - javax.servlet.ServletContextListener. В инфраструктуре Spring, класс, реализующий этот интерфейс, называется org.springframework.web.context.ContextLoaderListener. Можно увидеть этот класс-обработчик в первом теге <listener> в листинге 1.

Точно так же JSF предоставляет класс com.sun.faces.config.ConfigureListener, который реализует несколько интерфейсов для обработки событий. Класс ConfigureListener можно найти во втором теге <listener> в листинге 1.

Далее в статье рассматриваются различные интерфейсы для обработки событий и обработку данных, выполняющуюся внутри классов обработчиков событий Acegi и JSF (см. разделы "Запуск JSF-Acegi приложения" и "Обработка запроса к JSF-странице, защищенной Acegi").

Конфигурирование и привязка к URL-шаблонам фильтров сервлетов

Теперь обратите внимание на тег <filter> в листинге 1. Сервлет-приложения используют фильтры для предварительной обработки входящих запросов, прежде чем запрашиваемый сервлет сможет обработать их. Acegi использует фильтры сервлетов для аутентификации пользователей перед обработкой запроса.

В теге <filter> в листинге 1 есть дочерний элемент <filter-class>, определяющий класс org.acegisecurity.util.FilterToBeanProxy. Класс FilterToBeanProxy - это один из компонентов Acegi. Этот класс реализует интерфейс, javax.servlet.Filter, который входит в спецификацию сервлетов. В интерфейсе javax.servlet.Filter есть метод doFilter(), который вызывается сервлет-контейнером при получении запроса.

Также в листинге 1 у тега <filter> есть другой дочерний элемент <init-param>. Тег <init-param> определяет параметры, необходимые для создания экземпляра класса FilterToBeanProxy. Как видно из листинга 1, классу FilterToBeanProxy необходим только один параметр - объект класса FilterChainProxy. Класс FilterChainProxy представляет полную цепочку фильтров Acegi, обсуждавшихся в первой части (см. раздел "Security Filters"). Метод doFilter() класса FilterToBeanProxy использует класс FilterChainProxy для запуска цепочки фильтров безопасности Acegi.

Тег <filter-mapping> в листинге 1 определяет ULR запроса, при котором вызывается FilterToBeanProxy. В данном примере все JSF-страницы просто привязаны к классу Acegi FilterToBeanProxy. Это значит, что управление автоматически переходит в метод FilterChainProxydoFilter(), когда пользователь пытается получить доступ к JSF-странице.

Конфигурация JSF-сервлета

Тег <servlet> в файле web.xml определяет сервлет (в данном случае - JSF-сервлет), который можно вызывать по определенному URL. Тег <servlet-mapping> определяет этот URL. Практически все JSP- или сервлет-приложения содержат эти два тега, так что нет необходимости их подробного обсуждения. (В разделе Ресурсы приведены ссылки на материалы, в которых рассматриваются основы программирования Java-сервлетов.)

К этому моменту были рассмотрены все теги в файле web.xml, необходимые для развертывания Acegi для защиты JSF-приложения. Также было показано, как обработчики событий, сервлеты и фильтры взаимодействуют друг с другом. Как можно предположить, если установить файл web.xml из листинга 1 в сервлет-контейнер, Acegi и JSF попробуют выполнить определенные действия в следующих двух случаях:

  • при запуске приложения;
  • когда приложение получает запрос к JSF-странице.

В следующих двух разделах разбираются последовательности событий, происходящих в этих двух случаях.


Запуск JSF-Acegi приложения

На рисунке 1 показана последовательность событий, которые происходят при запуске JSF-Acegi приложения:

Рисунок 1. Последовательность событий при запуске JSF-Acegi приложения
Рисунок 1. Последовательность событий при запуске JSF-Acegi приложения

Более подробно последовательность событий, изображенная на рисунке 1, описана ниже:

  1. Сервлет-контейнер создает экземпляры всех обработчиков, сконфигурированных в файле web.xml.
  2. Сервлет-контейнер регистрирует класс Acegi ContextLoaderListener в качестве класса-обработчика, который реализует интерфейс javax.servlet.ServletContextListener. Этот интерфейс ServletContextListener содержит два важных метода: contextInitialized() и contextDestroyed():
    • Метод contextInitialized() получает управление, когда инициализируется контекст сервлета.
    • Сходным образом метод contextDestroyed() вызывается при удалении контекста сервлета во время завершения работы приложения.
  3. Сервлет контейнер регистрирует класс JSF ConfigureListener в качестве другого обработчика событий. Класс JSF ConfigureListener реализует сразу несколько интерфейсов для обработки событий, таких как ServletContextListener, ServletContextAttributeListener, ServletRequestListener и ServletRequestAttributeListener. Методы интерфейса ServletContextListener уже рассматривались. Остальные интерфейсы - это:
    • ServletContextAttributeListener, содержащий три метода: attributeAdded(), attributeRemoved() и attributeReplaced(). Соответственно эти методы получают управление, когда атрибут добавляется в контекст сервлета, удаляется из контекста сервлета или заменяется новым атрибутом. Метод attributeReplaced() получает управление на шаге 8 в последовательности действий при обработке запроса к JSF-странице, защищенной Acegi.
    • ServletRequestListener, который содержит методы, получающие управление когда новый объект запроса сервлета создается или удаляется. Этот объект запроса для сервлета служит оболочкой для запроса от пользователя.
    • ServletRequestAttributeListener, содержащий методы, которые получают управление, когда атрибут добавляется, удаляется или заменяется в запросе сервлета. Дальше в статье обсуждается обработка, которую выполняет класс JSF ConfigureListener, когда новый объект запроса сервлета создается на шаге 3 последовательности действий при обработке запроса к JSF-странице, защищенной Acegi.
  4. Сервлет-контейнер создает объект контекста сервлета, который служит оболочкой для ресурсов приложения (таких как JSP-страницы, Java-классы и параметры инициализации приложения) и позволяет всему приложению получать доступ к ресурсам. Все другие компоненты JSF-Acegi приложения (обработчики, фильтры и сервлеты) хранят информацию о ресурсах приложения в форме атрибутов в объекте контекста сервлета.
  5. Сервлет-контейнер извещает компонент Acegi ContextLoaderListener об инициализации контекста сервлета вызовом метода contextInitializated() интерфейса ContextLoaderListener.
  6. Метод contextInitialized() анализирует конфигурационный файл Acegi, создает контекст Web-приложения для JSF-Acegi приложения и создает экземпляры всех фильтров безопасности и Java beans-компонентов, настроенных в конфигурационном файле Acegi. Эти экземпляры фильтров затем используются при аутентификации и авторизации, когда JSF-приложение получает запрос от клиента (см. описание создания контекста Web-приложения, относящееся к рисунку 1 в третьей части серии).
  7. Сервлет-контейнер оповещает компонент JSF ConfigureListener об инициализации контекста сервлета вызовом метода contextInitialized().
  8. Метод contextInitialized() проверяет все Java beans-компоненты, управляемые JSF и определенные в конфигурационных файлах JSF, чтобы убедиться, что для каждого bean-компонента существуют Java-классы.
  9. Сервлет-контейнер проверяет файл web.xml на предмет наличия любых сконфигурированных фильтров. Например, в файле web.xml в листинге 1 содержится фильтр Acegi с названием FilterToBeanProxy, экземпляр которого сервлет-контейнер создает, инициализирует и регистрирует в качестве фильтра. Теперь Acegi готова для обработки входящих запросов на предмет аутентификации и авторизации.
  10. Сервлет-контейнер создает экземпляр Faces сервлета, который начинает ожидать входящих запросов от пользователей.

В следующем разделе рассматривается последовательность событий, возникающих, когда JSF-Acegi приложение получает запрос от пользователя.


Обработка запроса к JSF-странице, защищенной Acegi

Ранее было рассказано, как настроить Acegi для защиты JSF-приложения. Также была рассмотрена последовательность событий, происходящих при запуске JSF-Acegi приложения. В этом разделе описывается, как компоненты JSF и Acegi работают вместе внутри инфраструктуры сервлет-контейнера, когда пользователь отправляет запрос к JSF-странице, защищенной Acegi.

На рисунке 2 представлена последовательность событий, происходящих, когда пользователь отправляет запрос к JSF-странице, защищенной Acegi:

Рисунок 2. Совместная работа JSF и Acegi для выдачи JSF-страницы клиенту
Рисунок 2. Совместная работа JSF и Acegi для выдачи JSF-страницы клиенту

Подробно последовательность событий, изображенных на рисунке 2, описана ниже:

  1. Сервлет-контейнер создает объект запроса сервлета, представляющий запрос от пользователя.
  2. Если вернуться к шагу 3 в последовательности действий при запуске JSF-Acegi приложения, то там говорится, что класс JSF ConfigureListener реализует интерфейс ServletRequestListener. Это значит, что ConfigureListener обрабатывает события, относящиеся к созданию и удалению объектов запроса сервлета. Поэтому сервлет-контейнер вызывает метод requestInitialized() класса ConfigureListener.
  3. Метод requestInitialized() готовится к выполнению жизненного цикла запроса, как это описано в спецификации JSF. Подготовка включает в себя проверку того, что для запроса существует Faces-контекст (Faces context - контекст JSF-компонентов). Faces-контекст служит оболочкой для ресурсов приложения, которые позже потребуются Faces-сервлету для выполнения жизненного цикла JSF. Faces-контекст отсутствует, если этот запрос является первым в новом сеансе взаимодействия с пользователем. В этом случае метод requestInitialized() создает новый Faces-контекст.
  4. Сервлет-контейнер проверяет, имеется ли в запросе пользователя какая-либо информация о состоянии. Если сервлет-контейнер не находит информации о состоянии, он предполагает, что этот запрос является первым в новом сеансе взаимодействия с пользователем, и создает для пользователя объект HTTP-сеанса. Если сервлет-контейнер обнаруживает, что запрос содержит информацию о состоянии (например, cookie или информацию о состоянии в строке URL), он восстанавливает предыдущий сеанс взаимодействия с пользователем, основываясь на полученной информации о сеансе.
  5. Сервлет-контейнер сопоставляет URL запроса с URL-шаблоном, определенным внутри дочернего элемента <url-pattern> тега <filter-mapping> в дескрипторе развертывания. Если URL-запроса соответствует URL-шаблону, то сервлет-контейнер вызывает компонент Acegi FilterToBeanProxy, зарегистрированный как фильтр сервлета на шаге 9 рисунка 1.
  6. FilterToBeanProxy использует класс FilterChainProxy для прохождения по всей цепочке фильтров безопасности Acegi. Фильтры Acegi автоматически проверяют объект HTTP-сеанса, созданный на шаге 4, для проверки того, что запрашивающий клиент уже аутентифицирован. Если Acegi обнаруживает, что пользователь не аутентифицирован, она выдает страницу для входа в систему. В противном случае она напрямую обращается к процессу авторизации, описанному в разделе "Configuring the interceptor" второй части.
  7. Acegi обновляет контекст сервлета, добавляя информацию об аутентифицированном сеансе взаимодействия с пользователем.
  8. Сервлет-контейнер извещает метод attributeReplaced() класса JSF ConfigureListener, что контекст сервлета был обновлен. Компонент ConfigureListener проверяет, были ли изменены какие либо из JSF Java Beans-компонентов. Если обнаруживаются какие-либо изменения, то Faces-контекст обновляется соответствующим образом. Однако в этом случае во время аутентификации Acegi не изменяет Java beans-компоненты, управляемые JSF, так что ConfigureListener не выполняет никакой работы во время вызова этого метода.
  9. Если процесс авторизации проходит успешно, то управление передается в Faces-сервлет, который выполняет жизненный цикл JSF и готовит ответ для отправки обратно клиенту.

Теперь, когда было рассмотрено, как JSF и Acegi работаю вместе для обработки JSF-запроса, пришло время на практике изучить совместную работу JSF и Acegi.


Пример JSF-Acegi приложения

Материалы для скачивания, представленные в этой статье (см. раздел Загрузки), содержат пример JSF-Acegi приложения - JSFAcegiSample, в котором демонстрируется простая интеграция Acegi и JSF. В примере используется файл web.xml, приведенный в листинге 1.

Для установки примера приложения необходимо выполнить два действия из раздела "Развертывание и запуск приложения" в первой части. Также потребуется загрузить и разархивировать файл jsf-1_1_01.zip с сайта Sun JSF (см. раздел Ресурсы), а затем скопировать файлы из архива jsf-1.1.X.zip в папку WEB-INF/lib в каталоге JSFAcegiSample.

Пример приложения можно вызвать, введя в Web-браузере следующий URL: http://localhost:8080/JSFAcegiSample. Пример JSFAcegiSample отображает стартовую страницу, содержащую ссылки на защищенные ресурсы и страницу для входа в систему. Все защищенные компоненты разработаны с использованием JSF-компонентов, тогда как Acegi предоставляет страницу для входа в систему и выполняет аутентификацию и авторизацию.


Заключение

В этой статье рассказывается, как настроить Acegi для защиты JSF-приложений. Также подробно разобрано, как JSF и Acegi компоненты работают вместе внутри инфраструктуры сервлет-контейнера. В конце приведен пример запуска JSF-Acegi приложения.

Есть еще несколько аспектов, касающихся реализации безопасности JSF-приложений с помощью Acegi, которые стоит рассмотреть. В следующей статье этой серии рассматривается, как использовать Acegi для организации защиты доступа к Java Beans-компонентам, управляемым JSF.


Загрузка

ОписаниеИмяРазмер
исходный код для этой статьиj-acegi4-source.zip15KB

Ресурсы

Научиться

Получить продукты и технологии

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Технология Java
ArticleID=435318
ArticleTitle=Обеспечение безопасности Java-приложений с помощью Acegi: Часть 4. Защита JSF-приложений
publish-date=10132009