«1. Обзор
В этом руководстве мы рассмотрим службу Apereo Central Authentication Service (CAS) и увидим, как служба Spring Boot может использовать ее для аутентификации. CAS — это корпоративное решение единого входа (SSO) с открытым исходным кодом.
2. Настройка CAS-сервера
2.1. Установка CAS и зависимости
Сервер использует стиль Maven (Gradle) War Overlay для упрощения установки и развертывания:
git clone https://github.com/apereo/cas-overlay-template.git cas-server
Эта команда клонирует шаблон cas-overlay-template в каталог cas-server.
Некоторые из аспектов, которые мы рассмотрим, включают регистрацию службы JSON и подключение к базе данных JDBC. Итак, мы добавим их модули в раздел зависимостей файла build.gradle:
compile "org.apereo.cas:cas-server-support-json-service-registry:${casServerVersion}"
compile "org.apereo.cas:cas-server-support-jdbc:${casServerVersion}"
Обязательно проверим последнюю версию casServer.
2.2. Конфигурация сервера CAS
Прежде чем мы сможем запустить сервер CAS, нам нужно добавить некоторые базовые конфигурации. Начнем с создания папки cas-server/src/main/resources и в этой папке. За этим последует создание application.properties и в папке:
server.port=8443
spring.main.allow-bean-definition-overriding=true
server.ssl.key-store=classpath:/etc/cas/thekeystore
server.ssl.key-store-password=changeit
Let’s proceed with the creation of the key-store file referenced in the configuration above. First, we need to create the folders /etc/cas and /etc/cas/config in cas-server/src/main/resources.
Then, we need to change the directory to cas-server/src/main/resources/etc/cas and run the command to generate thekeystore:
keytool -genkey -keyalg RSA -alias thekeystore -keystore thekeystore -storepass changeit -validity 360 -keysize 2048
keytool -importkeystore -srckeystore thekeystore -destkeystore $JAVA11_HOME/jre/lib/security/cacerts
The password for the source and destination keystore is changeit. On Unix systems, we may have to run this command with admin (sudo) privilege. After importing, we should restart all instances of Java that’s running or restart the system.
We’re using JDK11 because it’s required by CAS version 6.1.x. Also, we defined the environment variable $JAVA11_HOME that points to its home directory. We can now start the CAS server:
./gradlew run -Dorg.gradle.java.home=$JAVA11_HOME
Чтобы у нас не было ошибки рукопожатия SSL, мы должны использовать localhost в качестве значения имени и фамилии . Мы должны использовать то же самое для названия организации и подразделения. Кроме того, нам нужно импортировать хранилище ключей в JDK/JRE, которое мы будем использовать для запуска нашего клиентского приложения:
Когда приложение запустится, на терминале появится надпись READY. и сервер будет доступен по адресу https://localhost:8443.
cas.authn.accept.users=casuser::Mellon
2.3. Конфигурация пользователя сервера CAS
task run(group: "build", description: "Run the CAS web application in embedded container mode") {
dependsOn 'build'
doLast {
def casRunArgs = new ArrayList<>(Arrays.asList(
"-server -noverify -Xmx2048M -XX:+TieredCompilation -XX:TieredStopAtLevel=1".split(" ")))
if (project.hasProperty('args')) {
casRunArgs.addAll(project.args.split('\\s+'))
}
javaexec {
main = "-jar"
jvmArgs = casRunArgs
args = ["build/libs/${casWebApplicationBinaryName}"]
logger.info "Started ${commandLine}"
}
}
}
Мы пока не можем войти в систему, так как мы не настроили ни одного пользователя. CAS имеет различные методы управления конфигурацией, включая автономный режим. Давайте создадим папку конфигурации cas-server/src/main/resources/etc/cas/config, в которой мы создадим файл свойств cas.properties. Теперь мы можем определить статического пользователя в файле свойств:
./gradlew run
-Dorg.gradle.java.home=$JAVA11_HOME
-Pargs="-Dcas.standalone.configurationDirectory=/cas-server/src/main/resources/etc/cas/config"
Мы должны сообщить расположение папки конфигурации серверу CAS, чтобы настройки вступили в силу. Давайте обновим tasks.gradle, чтобы мы могли передавать местоположение в качестве аргумента JVM из командной строки:
Затем мы сохраняем файл и запускаем:
Обратите внимание, что значение cas.standalone. ConfigurationDirectory — это абсолютный путь. Теперь мы можем перейти на https://localhost:8443 и войти в систему с именем пользователя casuser и паролем Mellon.
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-cas</artifactId>
<versionId>5.3.0.RELEASE</versionId>
</dependency>
3. Настройка клиента CAS
server.port=8900
spring.freemarker.suffix=.ftl
Мы будем использовать Spring Initializr для создания клиентского приложения Spring Boot. Он будет иметь зависимости Web, Security, Freemarker и DevTools. Кроме того, мы также добавим зависимость для модуля CAS Spring Security в его pom.xml:
Наконец, давайте добавим следующие свойства Spring Boot для настройки приложения:
4. Сервер CAS Регистрация службы
Клиентские приложения должны зарегистрироваться на сервере CAS перед любой аутентификацией. Сервер CAS поддерживает использование клиентских реестров YAML, JSON, MongoDB и LDAP.
{
"@class" : "org.apereo.cas.services.RegexRegisteredService",
"serviceId" : "http://localhost:8900/login/cas",
"name" : "casSecuredApp",
"id" : 8900,
"logoutType" : "BACK_CHANNEL",
"logoutUrl" : "http://localhost:8900/exit/cas"
}
В этом руководстве мы будем использовать метод JSON Service Registry. Создадим еще одну папку cas-server/src/main/resources/etc/cas/services. Именно в этой папке будут храниться файлы JSON реестра службы.
Мы создадим файл JSON, содержащий определение нашего клиентского приложения. Имя файла casSecuredApp-8900.json соответствует шаблону serviceName-Id.json:
cas.serviceRegistry.initFromJson=true
cas.serviceRegistry.json.location=classpath:/etc/cas/services
Атрибут serviceId определяет шаблон регулярного выражения URL-адреса для клиентского приложения. Шаблон должен соответствовать URL-адресу клиентского приложения.
Атрибут id должен быть уникальным. Другими словами, не должно быть двух или более служб с одинаковым идентификатором, зарегистрированных на одном и том же сервере CAS. Наличие дублирующегося идентификатора приведет к конфликтам и переопределению конфигураций.
5. Конфигурация единого входа клиента CAS
@Bean
public CasAuthenticationFilter casAuthenticationFilter(
AuthenticationManager authenticationManager,
ServiceProperties serviceProperties) throws Exception {
CasAuthenticationFilter filter = new CasAuthenticationFilter();
filter.setAuthenticationManager(authenticationManager);
filter.setServiceProperties(serviceProperties);
return filter;
}
@Bean
public ServiceProperties serviceProperties() {
logger.info("service properties");
ServiceProperties serviceProperties = new ServiceProperties();
serviceProperties.setService("http://cas-client:8900/login/cas");
serviceProperties.setSendRenew(false);
return serviceProperties;
}
@Bean
public TicketValidator ticketValidator() {
return new Cas30ServiceTicketValidator("https://localhost:8443");
}
@Bean
public CasAuthenticationProvider casAuthenticationProvider(
TicketValidator ticketValidator,
ServiceProperties serviceProperties) {
CasAuthenticationProvider provider = new CasAuthenticationProvider();
provider.setServiceProperties(serviceProperties);
provider.setTicketValidator(ticketValidator);
provider.setUserDetailsService(
s -> new User("[email protected]", "Mellon", true, true, true, true,
AuthorityUtils.createAuthorityList("ROLE_ADMIN")));
provider.setKey("CAS_PROVIDER_LOCALHOST_8900");
return provider;
}
The ServiceProperties bean has the same URL as the serviceId in casSecuredApp-8900.json. This is important because it identifies this client to the CAS server.
The sendRenew property of ServiceProperties is set to false. This means a user only needs to present login credentials to the server once.
The AuthenticationEntryPoint bean will handle authentication exceptions. Thus, it’ll redirect the user to the login URL of the CAS server for authentication.
In summary, the authentication flow goes:
- A user attempts to access a secure page, which triggers an authentication exception
- The exception triggers AuthenticationEntryPoint. In response, the AuthenticationEntryPoint will take the user to the CAS server login page – https://localhost:8443/login
- On successful authentication, the server redirects back to the client with a ticket
- CasAuthenticationFilter will pick up the redirect and call CasAuthenticationProvider
- CasAuthenticationProvider will use TicketValidator to confirm the presented ticket on CAS server
- If the ticket is valid, the user will get a redirection to the requested secure URL
Следующим шагом для нас является настройка Spring Security для работы с сервером CAS. Мы также должны проверить полный поток взаимодействий, называемый последовательностью CAS.
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers( "/secured", "/login")
.authenticated()
.and().exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint());
}
Давайте добавим следующие конфигурации компонентов в класс CasSecuredApplication нашего приложения Spring Boot:
«
«Наконец, давайте настроим HttpSecurity для защиты некоторых маршрутов в WebSecurityConfig. В процессе мы также добавим точку входа аутентификации для обработки исключений:
-
6. Конфигурация единого выхода клиента CAS
We’ll first put in place logout on the client application and then extend it to single logout on the CAS server.
In order to make obvious what goes on behind the scene, we’ll create a logout() method to handle the local logout. On success, it’ll redirect us to a page with a link for single logout:
@GetMapping("/logout")
public String logout(
HttpServletRequest request,
HttpServletResponse response,
SecurityContextLogoutHandler logoutHandler) {
Authentication auth = SecurityContextHolder
.getContext().getAuthentication();
logoutHandler.logout(request, response, auth );
new CookieClearingLogoutHandler(
AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY)
.logout(request, response, auth);
return "auth/logout";
}
До сих пор мы имели дело с единым входом в систему; давайте теперь рассмотрим единый выход CAS (SLO).
Приложения, использующие CAS для управления аутентификацией пользователей, могут выполнять выход пользователя из системы из двух мест:
@Bean
public SecurityContextLogoutHandler securityContextLogoutHandler() {
return new SecurityContextLogoutHandler();
}
@Bean
public LogoutFilter logoutFilter() {
LogoutFilter logoutFilter = new LogoutFilter("https://localhost:8443/logout",
securityContextLogoutHandler());
logoutFilter.setFilterProcessesUrl("/logout/cas");
return logoutFilter;
}
@Bean
public SingleSignOutFilter singleSignOutFilter() {
SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();
singleSignOutFilter.setCasServerUrlPrefix("https://localhost:8443");
singleSignOutFilter.setLogoutCallbackPath("/exit/cas");
singleSignOutFilter.setIgnoreInitConfiguration(true);
return singleSignOutFilter;
}
The logoutFilter will intercept requests to /logout/cas and redirect the application to the CAS server. The SingleSignOutFilter will intercept requests coming from the CAS server and perform the local logout.
Клиентское приложение может выполнить выход пользователя из себя локально — это не повлияет на статус входа пользователя в другие приложения, использующие тот же Сервер CAS Клиентское приложение также может выполнить выход пользователя из сервера CAS — это приведет к выходу пользователя из всех других клиентских приложений, подключенных к тому же серверу CAS.
В процессе единого выхода сервер CAS сначала истечет срок действия билета пользователя, а затем отправит асинхронный запрос всем зарегистрированным клиентским приложениям. Каждое клиентское приложение, получившее этот сигнал, выполнит локальный выход. Таким образом, достижение цели выхода из системы один раз приведет к выходу из системы везде.
cas.authn.accept.users=
cas.authn.jdbc.query[0].sql=SELECT * FROM users WHERE email = ?
cas.authn.jdbc.query[0].url=
jdbc:mysql://127.0.0.1:3306/test?
useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
cas.authn.jdbc.query[0].dialect=org.hibernate.dialect.MySQLDialect
cas.authn.jdbc.query[0].user=root
cas.authn.jdbc.query[0].password=root
cas.authn.jdbc.query[0].ddlAuto=none
cas.authn.jdbc.query[0].driverClass=com.mysql.cj.jdbc.Driver
cas.authn.jdbc.query[0].fieldPassword=password
cas.authn.jdbc.query[0].passwordEncoder.type=NONE
We set the cas.authn.accept.users to blank. This will deactivate the use of static user repositories by the CAS server.
According to the SQL above, users’ credentials are stored in the users table. The email column is what represents the users’ principal (username).
Please make sure to check the list of supported databases, available drivers and dialects. We also set the password encoder type to NONE. Other encryption mechanisms and their peculiar properties are also available.
Note that the principal in the database of the CAS server must be the same as that of the client application.
Let’s update CasAuthenticationProvider to have the same username as the CAS server:
@Bean
public CasAuthenticationProvider casAuthenticationProvider() {
CasAuthenticationProvider provider = new CasAuthenticationProvider();
provider.setServiceProperties(serviceProperties());
provider.setTicketValidator(ticketValidator());
provider.setUserDetailsService(
s -> new User("[email protected]", "Mellon", true, true, true, true,
AuthorityUtils.createAuthorityList("ROLE_ADMIN")));
provider.setKey("CAS_PROVIDER_LOCALHOST_8900");
return provider;
}
Сказав это, давайте добавим некоторые конфигурации компонентов в наше клиентское приложение. В частности, в CasSecuredApplicaiton:
7. Подключение сервера CAS к базе данных
Мы можем настроить сервер CAS для чтения учетных данных из базы данных MySQL. Мы будем использовать тестовую базу данных сервера MySQL, работающего на локальном компьютере. Обновим cas-server/src/main/resources/etc/cas/config/cas.properties:
CasAuthenticationProvider не использует пароль для аутентификации. Тем не менее, его имя пользователя должно совпадать с именем сервера CAS для успешной аутентификации. Сервер CAS требует, чтобы сервер MySQL работал на локальном хосте с портом 3306. Имя пользователя и пароль должны быть root.