четверг, 12 августа 2010 г.

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

Продолжим цикл статей, посвященных настройке аутенфикации веб-приложений с использованием SSO JA-SIG CAS. В этой статье предлагается рассмотреть, каким образом настроить веб-приложение, в основе которого лежит Spring Framework 3-й версии.
Spring Framework стал одним из стандартов де-факто в разработке корпоративных веб-приложений. Вышедшая не так давно, 3-я версия этого замечательного фреймворка позволила сделать процесс разработки еще более простым и увлекательным. Как можно будет убедиться, интеграция Spring Framework и JA-SIG CAS не займет много времени и не потребует особых усилий.
Скорее всего вам уже известно, что вопросами аутенфикации и разграничения доступа в Spring Framework отдано на откуп отдельной подсистеме - Spring Security. В третьей версии Spring интеграция с Security стала еще более плотной. На момент написания статьи актуальной является версия 3.0.3.RELEASE (репутация более молодых версий подпорчена неприятным security багом). Для того чтобы избежать проблем с зависимостями библиотек, настойчиво рекомендую использовать для java-разработки apache maven. В этом случае, для подключения Spring Security и CAS, достаточно будет указать в разделе dependencies файла pom.xml следующие блоки:

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-core</artifactId>
    <version>${org.springframework.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>${org.springframework.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-taglibs</artifactId>
    <version>${org.springframework.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-cas-client</artifactId>
    <version>${org.springframework.version}</version>
</dependency>

Не забудьте определить значение переменной ${org.springframework.version} в разделе properties.
Как можно видеть, от стандартной конфигурации зависимостей Spring Security, указанная выше, отличается наличием дополнительного модуля spring-security-cas-client. Данная библиотека содержит все классы, необходимые для интеграции Spring Security и JA-SIG CAS.
В работе SSO аутенфикации активно используется механизм сессий. Это приводит к необходимости внести определенные изменения в порядок обработки http-запросов. Нашей следующей целью становится файл web.xml. Рекомендуется добавить следующие определения к стандартной конфигурации:

<filter>
    <filter-name>CAS Single Sign Out Filter</filter-name>
    <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>
<filter-mapping>
    <filter-name>CAS Single Sign Out Filter</filter-name>
    <url-pattern>/*</url-mapping>
</filter-mapping>

<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-mapping>
</filter-mapping>

<listener>
    <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
</listener>

После редактирования данных файлов наступает самый объемный этап работы, а именно, формирование файла конфигурации Spring Security. Общепринятым названием для данного файла является securityContext.xml. Последние версии Spring Security опираются на собственное именное пространство конфигурации, а также на настройки по-умолчанию, что позволяет довольно быстро и в лаконичной форме получать работоспособную систему разграничения доступа и аутенфикации. Перепробовав несколько различных конфигураций, я пришел к следующему рабочему варианту:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:security="http://www.springframework.org/schema/security"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/security 
        http://www.springframework.org/schema/security/spring-security-3.0.xsd">


    <security:http entry-point-ref="casEntryPoint">
        <security:intercept-url pattern="/admin" access="ROLE_ADMIN"/>
        <security:intercept-url pattern="/login" access="ROLE_USER"/>
        <security:intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
        <!--suppress SpringModelInspection -->
        <security:custom-filter position="CAS_FILTER" ref="casFilter"/>
        <security:logout invalidate-session="true" logout-url="/logout" logout-success-url="/"/>
    </security:http>

    <bean id="casEntryPoint" class="org.springframework.security.cas.web.CasAuthenticationEntryPoint">
        <property name="loginUrl" value="https://cas.server.net:8443/login"/>
        <property name="serviceProperties" ref="serviceProperties"/>
    </bean>

    <bean id="serviceProperties" class="org.springframework.security.cas.ServiceProperties">
        <property name="service" value="http://my_spring_service/j_spring_cas_security_check"/>
        <property name="sendRenew" value="true"/>
    </bean>

    <bean id="casFilter" class="org.springframework.security.cas.web.CasAuthenticationFilter">
        <property name="authenticationManager" ref="authenticationManager"/>
    </bean>

    
    <security:authentication-manager alias="authenticationManager">
        <security:authentication-provider ref="casAuthenticationProvider"/>
    </security:authentication-manager>

    <bean id="casAuthenticationProvider" class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
        <property name="userDetailsService" ref="userService"/>
        <property name="serviceProperties" ref="serviceProperties"/>
        <property name="ticketValidator">
            <bean class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator">
                <constructor-arg index="0" value="https://cas.server.net:8443/"/>
            </bean>
        </property>
        <property name="key" value="an_id_for_this_auth_provider_only"/>
    </bean>

    <bean id="userService" class="class that implements spring userService">
    </bean>
</beans>

Головным блоком, определяющим настройки Spring Security, является <security:http>. Для данного блока мы указываем точку аутенфикации, перечисляем роли доступа для тех или иных частей ресурса,  добавляем cas-filter, и url выхода. Так в данном конфигурационном файле указано, что точка аутенфикации описывается компонентой casEntryPoint, раздел /admin требует от пользователя роли администратора, /login - роли пользователя, остальные части ресурса доступны для анонимных пользователей. Добавление casFilter необходимо для успешной обработки квитанции, получаемой от CAS сервера в случае успешной аутенфикации.  Если перейти по url "/logout", то произойдет инвалидация сессии, и пользователь, став анонимным, перенаправляется в корень приложения.
Компонент casEntryPoint определяет url страницы аутенфикации CAS-сервера (https://cas.server.net:8443/login), а также, через компонент serviceProperties, определяет url, который в случае удачной аутенфикации получит квитанцию от CAS-сервера. Данный url прослушивает CAS filter и по-умолчанию равен /j_spring_cas_security_check. Если есть необходимость изменить значение по-умолчанию, это можно сделать, указав соответствующее значение параметру filterProcessUrl бина casFilter. 
Основную роль в методе аутенфикации играет экземпляр провайдера аутенфикации. Фильтр CAS узнает о провайдере опосредованно, через ссылку на authenticationManager. Провайдеру необходимо указать способ проверки квитанции, полученной от CAS-сервера (Cas20ServiceTicketValidator), ключ идентификации, а также ссылку на экземпляр UserDetailsService. В моем случае, я использовал самописный UserDetailsService, который создавал экземпляр класса UserDetails с необходимыми ролями и списком переменных для приложения.
В конце,  можно запустить приложение и проверить работоспособность аутенфикации через сервер CAS. Хотелось бы напомнить, что для успешного общения с CAS-сервером по протоколу SSL необходимо доверительное отношение к сертификату сервера. В случае, если ваш сертификат самоподписной, не забудьте импортировать его в хранилище сертификатов java-виртуальной машины клиента. Как это делается, описано в предыдущей статье.
Данная конфигурация проста и незатейлива,  рассчитана на ситуацию, когда у приложения есть анонимные пользователи с общим доступом, и залогиненные пользователи (например, для предоставления дополнительных функций). Разграничение на основе списков доступа, и более сложные механизмы не рассматривались, но добавление данных возможностей в представленную конфигурацию не должно сильно отличаться от других ситуаций использования Spring Security.

Предыдущие статьи про настройку JA-SIG CAS:
  1. Начальная сборка CAS-сервера;
  2. Собственный обработчик аутенфикации CAS-сервера;
  3. Настройка SSL в  Apache Tomcat 6.

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