Приветствую, уважаемые хабро-читатели. Перед вами продолжение серии статей про JASIG CAS. В этой части я расскажу, как собрать артефакт CAS и начать с ним работать. Прежде, чем читать дальше, я надеюсь, вы прочитали первую часть.
Настроились, собрались… разобрались.
Как говорится, правильный настрой — половина дела. Для того, что бы правильно сконфигурировать и собрать свою версию CAS, вы должны хорошо представлять себе, как работают Spring и Maven.
Если вы хотите познать дао настройки CAS от начала до конца, то вам вот сюда. Я же расскажу про основные вещи, которые нужно знать, для того, что бы успешно запустить CAS в той среде, которую я описал в первой статье.
Для начала приведу список файлов, которые могут понадобиться для настройки. После этого, я остановлюсь подробнее на тех из них, которые считаю наиболее важными. Оригинал списка можно найти здесь.
Файлы конфигурации
- WEB-INF/classes/log4j.xml. В этом файле находятся настройки логирования. При желании можно добавлять свои логеры, CAS не накладывает никаких ограничений.
- WEB-INF/cas-servlet.xml. Это Spring MVC конфигурация сервлетов, обрабатывающих сервисные URL CAS-а
- WEB-INF/cas.properties. Содержит URL-ы служебных сервисов, SQL диалект и вообще любые свойства какие придумаетe, можно складывать сюда.
- WEB-INF/deployerConfigContext.xml. Почти все настройки сервиса находятся в этом файле.
- WEB-INF/login-webflow.xml. Здесь, с помощью Spring Web Flow, настраивается поток авторизации.
- WEB-INF/restlet-servlet.xml. Настройка RESTful APIs. По идее вам никогда не придется менять этот файл.
- WEB-INF/classes/messages_*.properties. Это набор property файлов, используемые для локализации CAS.
- WEB-INF/spring-configuration. В этой директории расположены настройки большинства компонентов. Многие из них можно смело переопределять.
Файлы конфигурации расположенные в WEB-INF/spring-configuration
- applicationContext.xml. Содержит стандартные бины, которые обычно не нужно переопределять, такие как планировщик Autowiring-а и настройки задач Quartz.
- ticketRegistry.xml. Реестр тикетов. Вам вряд ли захочется его менять.
- argumentExtractorsConfiguration.xml. Отвечает за выбор протокола. По умолчанию CAS и SAML.
- propertyFileConfigurer.xml. Указывает путь, откуда брать свойства. По умочанию из WEB-INF/cas.properties
- securityContext.xml.
- ticketExpirationPolicies.xml. Настройки связанные с созданием и валидацией тикетов.
- ticketGrantingTicketCookieGenerator.xml. Описывает способ создания TGT. Скорее всего менять его не придется.
deploymentconfigContext.xml
И так, ключевым файлом настройки CAS является deployerConfigContext.xml. Это довольно объемный файл, поэтому я буду разбирать его частями сверху вниз.
В самом начале файла располагается список резолверов учетных данных. Резолверы отвечают за извлечение учетных данных из запроса авторизации, приходящей на сервер.
<property name="credentialsToPrincipalResolvers">
...
</property>
Если вы не будете использовать ничего, кроме логина/пароля, переданных с формы авторизации, то этот раздел можно смело оставить как есть.
Дальше идет описание обработчиков, которые отвечают за то, как CAS будет использовать полученные учетные данные. Я использую их для поиска пользователя в LDAP
<property name="authenticationHandlers">
<list>
<bean class="org.jasig.cas.adaptors.ldap.BindLdapAuthenticationHandler"
p:filter="userLogin =%u"
p:searchBase=""
p:timeout="${ldapReadConnectTimeout}"
p:contextSource-ref="contextSource"/>
</property>
ВАЖНО! В продуктовой среде обязательно удалите SimpleTestUsernamePasswordAuthenticationHandler.
ВАЖНО! Обратите внимание на параметр p:timeout="${ldapReadConnectTimeout}". Это таймаут чтения/соединения с LDAP. Без установки этого параметра стандартные значения не будут переписаны, не смотря, даже, на установленные параметры com.sun.jndi.ldap.connect.timeout com.sun.jndi.ldap.read.timeout, о которых речь пойдет чуть позже.
Следующий блок конфигураций связан с настройкой источника данных о пользователях. Я приведу пример конфигурации, когда пользователи хранятся в openLdap.
Есть два способа поиска пользователей в LDAP:
- FastBindLdapAuthenticationHandler. Он наиболее быстрый, но подходит только в тех случаях, когда из учетных данных можно напрямую построить DN (distinguished name), т.е. uid= %u,ou=users,dc=domain. Где %u — это переданный логин.
- BindLdapAuthenticationHandler. Он чуть медленнее, но позволяет больше свободы в выборе нужной записи. Работает он в 2 этапа — с начал происходит LDAP bind. Учетные для bind берутся из contextSource, о котором я расскажу чуть позже. 2-этап — LDAP search c применением фильтра.
ВАЖНО! При работе с LDAP, base, для операций search и bind используется по-разному. Для bind всегда нужно указывать полное имя записи, т.е., например cn=user,dc=sso,dc=ru, где base — dc=sso,dc=ru. Поэтому base в настройке contextSource всегда должен быть заполнен и userDN — это всегда полное имя пользователя, под которым авторизуется CAS в LDAP.
Во время поиска searchBase добавляется к фильтру поиска для ограничения области сканирования. Он вполне может быть пустым, в этом случае критерий поиска в директории определяется только фильтром.
Пример настройки contextSource.
<bean id="contextSource" class="org.springframework.ldap.core.support.LdapContextSource">
<property name="pooled" value="false"/>
<property name="urls">
<list>
<value>ldap://your.host.nameORip</value>
</list>
</property>
<property name="userDn" value="cn=user,dc=subdomain,dc=domain"/>
<property name="password" value="verybigsecret"/>
<property name="base" value="dc=subdomain,dc=domain"/>
<property name="baseEnvironmentProperties">
<map>
<entry>
<key>
<value>java.naming.security.authentication</value>
</key>
<value>simple</value>
</entry>
<entry key="com.sun.jndi.ldap.connect.timeout" value="5000"/>
<entry key="com.sun.jndi.ldap.read.timeout" value="5000"/>
</map>
</property>
</bean>
Параметры com.sun.jndi.ldap.(connect, read).timeout отвечают за таймауты при соединении и чтении данных из LDAP соответственно.
С userDetailsService проблем возникнуть не должно, поэтому перейдем сразу к последней важной настройке — хранилищу сервисов. Есть несколько способов хранить зарегистрированные сервисы. По умолчанию используется inMemory хранилище. Этот вариант не подходит для продуктовой среды и тех случаев, когда вы хотите разделять один список сервисов среди нескольких CAS серверов. Мы используем MySql, развернутый, на той же машине, что и сервер CAS. Если такой вариант приемлем для вас, вы можете просто скопировать эту конфигурацию и подставить свои логинпароль.
<bean id="serviceRegistryDao"
class="org.jasig.cas.services.JpaServiceRegistryDaoImpl"
p:entityManagerFactory-ref="entityManagerFactory"/>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="generateDdl" value="true"/>
<property name="showSql" value="true"/>
</bean>
</property>
<property name="jpaProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
p:driverClassName="com.mysql.jdbc.Driver"
p:url="jdbc:mysql://localhost:3306/cas?autoReconnect=true"
p:username="root"
p:password=""/>
ticketExpirationPolicies.xml
Это еще один важный файл настройки. В нем описаны expiration policies для ST и TGT. Вам может понадобиться увеличить значения параметров для ST, если вы собираетесь изменять протокол взаимодействия, наблюдаются серьезные сетевые задержки или сервера, участвующие в SSO, плохо синхронизированы по времени.
cas.properties
Стандартных свойств здесь не так много.
cas.securityContext.serviceProperties.service. Значение его может быть например https:// localhost:8443/cas/services/j_acegi_cas_security_check. Это URL сервлета, который производит проверки тикетов. У нас он выглядит вот так: ${service} т.к. эту и многие другие настройки мы предпочитаем выносить в свойства MAVEN профилей и подставлять при сборке. Как это делается, я расскажу чуть позже.
cas.securityContext.serviceProperties.adminRoles=ROLE_ADMIN. Это свойство содержит названия ролей пользователей из Spring Security. Пользователи с этими ролями имеют доступ к служебным интерфейсам CAS. Эта настройка непосредственно связана с userDetailsService из deployerConfigContext.xml.
cas.securityContext.casProcessingFilterEntryPoint.loginUrl. По этому пути расположен сервлет, который принимает учетные данные пользователя.
cas.securityContext.ticketValidator.casServerUrlPrefix. URL CAS.
host.name. Как я понял, этот параметр используется в основном как префикс в именах TGT и ST. Так что можете выбрать любое название на ваш вкус.
database.hibernate.dialect. Это свойство определяет SQL диалект Hibernate. В deployerConfigContext.xml мы выбрали MySQl для хранения сервисов, соответственно его значение должно быть установлено в org.hibernate.dialect.MySQLDialect.
CAS позволяет задавать разное оформление для своих web интерфейсов, с помощью CAS themes. Как это делается, я расскажу, наверно, в другой статье. Подробно о темах можно почитать вот здесь.
Сборка
Теперь, когда с настройками разобрались, можно попробовать собрать свой собственный артефакт. Собирать будем с помощью Maven war overlay
Для начала опишем свойства, которые нам понадобятся позже
<properties>
<spring.version>3.0.4.RELEASE</spring.version>
<spring.security.version>3.0.3.RELEASE</spring.security.version>
<cas.version>3.4.8</cas.version>
<compiler.version>2.0.2</compiler.version>
<source.version>1.6</source.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
Важно! Обязательно укажите кодировку ресурсов, которая будет использована при сборке проекта. Иначе у вас могут возникнуть проблемы с русским текстом.
Добавляем в проект зависимости в соответствии с настройками, сделанными в deployerConfigContext.xml
<!— Исходный проект CAS для которого будет применен OVERLAY -->
<dependency>
<groupId>org.jasig.cas</groupId>
<artifactId>cas-server-webapp</artifactId>
<version>${cas.version}</version>
<type>war</type>
<scope>runtime</scope></dependency>
<!— Зависимости для работы с базой данных -->
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>3.6.0.Final</version>
<exclusions>
<exclusion>
<artifactId>hibernate-core</artifactId>
<groupId>org.hibernate</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.14</version>
</dependency>
</dependencies>
<!-- Зависимости для работы с LDAP-->
<dependency>
<groupId>org.jasig.cas</groupId>
<artifactId>cas-server-support-ldap</artifactId>
<version>${cas.version}</version>
<exclusions>
<exclusion>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
</exclusion>
<exclusion>
<artifactId>jaxb-impl</artifactId>
<groupId>com.sun.xml.bind</groupId>
</exclusion>
<exclusion>
<artifactId>spring-expression</artifactId>
<groupId>org.springframework</groupId>
</exclusion>
<exclusion>
<artifactId>spring-expression</artifactId>
<groupId>org.springframework</groupId>
</exclusion>
<exclusion>
<artifactId>spring-expression</artifactId>
<groupId>org.springframework</groupId>
</exclusion>
</exclusions>
</dependency>
Сборка с помощью overlay подходит, когда вам не нужны профили и фильтры ресурсов, но хочется использовать ресурсы другого проекта как базу для своей сборки.
Например, если нужно заменить deployerConfigContext.xml и cas.properties, в артефакте, это можно будет сделать вот так…
<plugin>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<warName>cas</warName>
<overlays>
<overlay>
<groupId>org.jasig.cas</groupId>
<artifactId>cas-server-webapp</artifactId>
<excludes>
<exclude>WEB-INF/deployerConfigContext.xml</exclude>
<exclude>WEB-INF/cas.properties</exclude>
</excludes>
</overlay>
</overlays>
</plugin>
Хотя этот способ довольно хорош я, все же, рекомендую воспользоваться профилями. Они дают значительно больше свободы в конфигурировании приложения. С их помощью, например, очень просто организовать сборку артефактов для продуктовой и тестовой сред.
Теперь build тег будет выглядеть вот так
<plugin>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<warName>cas</warName>
<webResources>
<resource>
<directory>src/main/config</directory>
<!-- override the destination directory for this resource -->
<targetPath>WEB-INF</targetPath>
<!-- enable filtering -->
<filtering>true</filtering>
</resource>
</webResources>
</configuration>
</plugin>
Теперь добавляем профили
<profiles>
<profile>
<id>test</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<service>
https://localhost:8443/cas/services/j_acegi_cas_security_check
</service>
</properties>
</profile>
<profile>
<id>prod</id>
<properties>
<service>
https://auth.tcsbank.ru:8443/cas/services/j_acegi_cas_security_check
</service>
</properties>
</profile>
</profiles>
В тег профилей разумно выносить все свойства, которые различаются в разных средах.
Теперь можно создать директорию /src/main/config и скопировать туда все ресурсы, для которых вы хотите проводить фильтрацию с помощью Maven. В итоге структура вашего проекта может выглядеть примерно так, как показано на картинке справа.
Осталось только собрать проект, например для продуктовой среды…
mvn -e clean compile package -P prod
Модификатор —e предписывает показывать более подробную информацию о процессе сборки, включая stacktrace любой возникшей ошибки.
Важно! Перед сборкой убедитесь, что для всех локалей, в которых предполагается использовать CAS, присутствуют все сообщения. Для этого нужно убедиться, что файл сообщений messages для нужных локалей содержат те же сообщения, что и файл для локали en. Если это не так, вам нужно скопировать recource Bundle в свой проект в директорию /webapp/WEB-INF/classes/ и вручную дополнить список сообщений. Иначе CAS не сможет отобразить ни один сервисный интерфейс, в котором отсутствуют нужные сообщения.
Взлетаем
Ну вот, CAS собран и запущен и от полноценного сервиса единой авторизации нас отделяет последний шаг — добавление доверенных сервисов. Подробную инструкцию как это сделать, можно узнать тут.
Важно! Первым сервисом, обязательно должен быть добавлен сам CAS. Если этого не сделать, после добавления любого другого сервиса, служебные интерфейсы станут недоступны и вам, скорее всего, придется вручную очищать хранилище сервисов или добавить в него CAS.
Для того, что бы добавить сервис в CAS нужно перейти на страницу управления сервисам по адресу https://${serverUrl}/services/add.html. Получить доступ на эту страницу, может только авторизованный пользователь. Роли для пользователей настраиваются в cas.properties и deployerConfigContext.xml.
URL сервисов поддерживают шаблонизацию в стиле ANT. Например, что бы добавить в доверенные все сервисы, расположенные на локальной машине, можно использовать вот такой паттерн — ht tp*://localhost:*/**.
Вот и все на этот раз. В 3-ей части я расскажу как бороться с некоторыми распространенными проблемами, оптимизировать производительность и авторизовываться с внешних форм и даже асинхронно.
Спасибо что дочитали до конца)
Автор: SergeyPopov