Автор оригинала: Anshul Bansal.
1. Обзор
В этом уроке мы рассмотрим Spring Security SAML с Okta в качестве поставщика удостоверений личности (IdP) .
2. Что Такое SAML?
Язык разметки утверждений безопасности (SAML) – это открытый стандарт, который позволяет IdP безопасно отправлять данные аутентификации и авторизации пользователя поставщику услуг (SP) . Он использует сообщения на основе XML для связи между IdP и SP.
Другими словами, когда пользователь пытается получить доступ к службе, он должен войти в систему с помощью IdP. После входа в систему IdP отправляет атрибуты SAML с данными авторизации и аутентификации в формате XML в SP.
Помимо обеспечения защищенного механизма передачи аутентификации, SAML также поддерживает единый вход (SSO) , позволяя пользователям входить в систему один раз и повторно использовать те же учетные данные для входа в другие поставщики услуг.
3. Настройка Okta SAML
Во-первых, в качестве предварительного условия мы должны создать учетную запись разработчика Okta .
3.1. Создайте Новое Приложение
Затем мы создадим новую интеграцию веб-приложений с поддержкой SAML 2.0:
Затем мы заполним общую информацию, такую как название приложения и логотип приложения:
3.2. Редактирование интеграции SAML
На этом шаге мы предоставим параметры SAML, такие как URL-адрес единого входа и URI аудитории:
Наконец, мы можем предоставить обратную связь о нашей интеграции:
3.3. Просмотр Инструкций по настройке
После завершения мы можем просмотреть инструкции по настройке нашего приложения Spring Boot:
Примечание: мы должны скопировать инструкции, такие как URL-адрес эмитента IdP и XML метаданных IdP, которые потребуются в дальнейшем в конфигурациях безопасности Spring:
4. Установка пружинной загрузки
Помимо обычных зависимостей Maven, таких как spring-boot-starter-web и spring-boot-starter-security , нам потребуется spring-security-saml2-core зависимость:
org.springframework.boot spring-boot-starter-web 2.4.2 org.springframework.boot spring-boot-starter-security 2.4.2 org.springframework.security.extensions spring-security-saml2-core 1.0.10.RELEASE
Кроме того, не забудьте добавить Shibboleth репозиторий для загрузки последней версии opensaml jar , необходимой для spring-security-saml2-core зависимости:
Shibboleth Shibboleth https://build.shibboleth.net/nexus/content/repositories/releases/
В качестве альтернативы мы можем настроить зависимости в проекте Gradle:
compile group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: "2.4.2" compile group: 'org.springframework.boot', name: 'spring-boot-starter-security', version: "2.4.2" compile group: 'org.springframework.security.extensions', name: 'spring-security-saml2-core', version: "1.0.10.RELEASE"
5. Конфигурация безопасности Пружины
Теперь, когда у нас есть готовая настройка Okta SAML и проект Spring Boot, давайте начнем с конфигураций безопасности Spring, необходимых для интеграции SAML 2.0 с Okta.
5.1. Точка входа SAML
Во-первых, мы создадим компонент класса SAMLEntryPoint , который будет работать в качестве точки входа для аутентификации SAML:
@Bean
public WebSSOProfileOptions defaultWebSSOProfileOptions() {
WebSSOProfileOptions webSSOProfileOptions = new WebSSOProfileOptions();
webSSOProfileOptions.setIncludeScoping(false);
return webSSOProfileOptions;
}
@Bean
public SAMLEntryPoint samlEntryPoint() {
SAMLEntryPoint samlEntryPoint = new SAMLEntryPoint();
samlEntryPoint.setDefaultProfileOptions(defaultWebSSOProfileOptions());
return samlEntryPoint;
}Здесь компонент WebSSOProfileOptions позволяет нам настроить параметры запроса, отправленного от SP к IdP с просьбой об аутентификации пользователя.
5.2. Вход и выход из системы
Далее, давайте создадим несколько фильтров для наших ОБРАЗЦОВ URI, таких как/ discovery, //login и/|/logout :
@Bean
public FilterChainProxy samlFilter() throws Exception {
List chains = new ArrayList<>();
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SSO/**"),
samlWebSSOProcessingFilter()));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/discovery/**"),
samlDiscovery()));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/login/**"),
samlEntryPoint));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/logout/**"),
samlLogoutFilter));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SingleLogout/**"),
samlLogoutProcessingFilter));
return new FilterChainProxy(chains);
} Затем мы добавим несколько соответствующих фильтров и обработчиков:
@Bean
public SAMLProcessingFilter samlWebSSOProcessingFilter() throws Exception {
SAMLProcessingFilter samlWebSSOProcessingFilter = new SAMLProcessingFilter();
samlWebSSOProcessingFilter.setAuthenticationManager(authenticationManager());
samlWebSSOProcessingFilter.setAuthenticationSuccessHandler(successRedirectHandler());
samlWebSSOProcessingFilter.setAuthenticationFailureHandler(authenticationFailureHandler());
return samlWebSSOProcessingFilter;
}
@Bean
public SAMLDiscovery samlDiscovery() {
SAMLDiscovery idpDiscovery = new SAMLDiscovery();
return idpDiscovery;
}
@Bean
public SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler() {
SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler = new SavedRequestAwareAuthenticationSuccessHandler();
successRedirectHandler.setDefaultTargetUrl("/home");
return successRedirectHandler;
}
@Bean
public SimpleUrlAuthenticationFailureHandler authenticationFailureHandler() {
SimpleUrlAuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();
failureHandler.setUseForward(true);
failureHandler.setDefaultFailureUrl("/error");
return failureHandler;
}До сих пор мы настроили точку входа для аутентификации ( samlEntryPoint ) и несколько цепочек фильтров. Итак, давайте глубоко погрузимся в их детали.
Когда пользователь пытается войти в систему в первый раз, samlEntryPoint обработает запрос на ввод. Затем компонент saml Discovery bean (если он включен) обнаружит IDP, с которым необходимо связаться для аутентификации.
Затем, когда пользователь входит в систему, IdP перенаправляет ответ SAML на /saml/sso URI для обработки , и соответствующий samlWebSSOProcessingFilter аутентифицирует соответствующий токен аутентификации.
В случае успеха successRedirectHandler перенаправит пользователя на целевой URL-адрес по умолчанию ( /home ). В противном случае authenticationFailureHandler перенаправит пользователя на URL-адрес /error .
Наконец, давайте добавим обработчики выхода для одиночных и глобальных выходов:
@Bean
public SimpleUrlLogoutSuccessHandler successLogoutHandler() {
SimpleUrlLogoutSuccessHandler successLogoutHandler = new SimpleUrlLogoutSuccessHandler();
successLogoutHandler.setDefaultTargetUrl("/");
return successLogoutHandler;
}
@Bean
public SecurityContextLogoutHandler logoutHandler() {
SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler();
logoutHandler.setInvalidateHttpSession(true);
logoutHandler.setClearAuthentication(true);
return logoutHandler;
}
@Bean
public SAMLLogoutProcessingFilter samlLogoutProcessingFilter() {
return new SAMLLogoutProcessingFilter(successLogoutHandler(), logoutHandler());
}
@Bean
public SAMLLogoutFilter samlLogoutFilter() {
return new SAMLLogoutFilter(successLogoutHandler(),
new LogoutHandler[] { logoutHandler() },
new LogoutHandler[] { logoutHandler() });
}5.3. Обработка метаданных
Теперь мы предоставим XML метаданных IdP для SP. Это поможет вашему провайдеру узнать, на какую конечную точку SP он должен перенаправить, как только пользователь войдет в систему.
Итак, мы настроим генератор метаданных bean, чтобы позволить Spring SAML обрабатывать метаданные:
public MetadataGenerator metadataGenerator() {
MetadataGenerator metadataGenerator = new MetadataGenerator();
metadataGenerator.setEntityId(samlAudience);
metadataGenerator.setExtendedMetadata(extendedMetadata());
metadataGenerator.setIncludeDiscoveryExtension(false);
metadataGenerator.setKeyManager(keyManager());
return metadataGenerator;
}
@Bean
public MetadataGeneratorFilter metadataGeneratorFilter() {
return new MetadataGeneratorFilter(metadataGenerator());
}
@Bean
public ExtendedMetadata extendedMetadata() {
ExtendedMetadata extendedMetadata = new ExtendedMetadata();
extendedMetadata.setIdpDiscoveryEnabled(false);
return extendedMetadata;
}Для Генератора метаданных bean требуется экземпляр Менеджера ключей для шифрования обмена между SP и IdP:
@Bean
public KeyManager keyManager() {
DefaultResourceLoader loader = new DefaultResourceLoader();
Resource storeFile = loader.getResource(samlKeystoreLocation);
Map passwords = new HashMap<>();
passwords.put(samlKeystoreAlias, samlKeystorePassword);
return new JKSKeyManager(storeFile, samlKeystorePassword, passwords, samlKeystoreAlias);
} Здесь мы должны создать и предоставить хранилище ключей для компонента KeyManager . Мы можем создать самозаверяющий ключ и хранилище ключей с помощью команды JRE:
keytool -genkeypair -alias baeldungspringsaml -keypass baeldungsamlokta -keystore saml-keystore.jks
5.4. Менеджер метаданных
Затем мы настроим метаданные IdP в нашем приложении Spring Boot с помощью экземпляра ExtendedMetadataDelegate :
@Bean
@Qualifier("okta")
public ExtendedMetadataDelegate oktaExtendedMetadataProvider() throws MetadataProviderException {
File metadata = null;
try {
metadata = new File("./src/main/resources/saml/metadata/sso.xml");
} catch (Exception e) {
e.printStackTrace();
}
FilesystemMetadataProvider provider = new FilesystemMetadataProvider(metadata);
provider.setParserPool(parserPool());
return new ExtendedMetadataDelegate(provider, extendedMetadata());
}
@Bean
@Qualifier("metadata")
public CachingMetadataManager metadata() throws MetadataProviderException, ResourceException {
List providers = new ArrayList<>();
providers.add(oktaExtendedMetadataProvider());
CachingMetadataManager metadataManager = new CachingMetadataManager(providers);
metadataManager.setDefaultIDP(defaultIdp);
return metadataManager;
} Здесь мы проанализировали метаданные из sso.xml файл, содержащий XML метаданных IdP, скопированный из учетной записи разработчика Okta при просмотре инструкций по установке.
Аналогично, переменная Idp по умолчанию содержит URL-адрес эмитента IdP, скопированный из учетной записи разработчика Okta.
5.5. Синтаксический анализ XML
Для синтаксического анализа XML мы можем использовать экземпляр класса StaticBasicParserPool :
@Bean(initMethod = "initialize")
public StaticBasicParserPool parserPool() {
return new StaticBasicParserPool();
}
@Bean(name = "parserPoolHolder")
public ParserPoolHolder parserPoolHolder() {
return new ParserPoolHolder();
}5.6. Процессор SAML
Затем мы требуем, чтобы процессор проанализировал сообщение SAML из HTTP-запроса:
@Bean
public HTTPPostBinding httpPostBinding() {
return new HTTPPostBinding(parserPool(), VelocityFactory.getEngine());
}
@Bean
public HTTPRedirectDeflateBinding httpRedirectDeflateBinding() {
return new HTTPRedirectDeflateBinding(parserPool());
}
@Bean
public SAMLProcessorImpl processor() {
ArrayList bindings = new ArrayList<>();
bindings.add(httpRedirectDeflateBinding());
bindings.add(httpPostBinding());
return new SAMLProcessorImpl(bindings);
} Здесь мы использовали привязки POST и Redirect в отношении нашей конфигурации в учетной записи разработчика Okta.
5.7. Реализация поставщика аутентификации SAML
Наконец, нам требуется пользовательская реализация класса SAML Authentication Provider для проверки экземпляра класса ExpiringUsernameAuthenticationToken и установки полученных полномочий:
public class CustomSAMLAuthenticationProvider extends SAMLAuthenticationProvider {
@Override
public Collection extends GrantedAuthority> getEntitlements(SAMLCredential credential, Object userDetail) {
if (userDetail instanceof ExpiringUsernameAuthenticationToken) {
List authorities = new ArrayList();
authorities.addAll(((ExpiringUsernameAuthenticationToken) userDetail).getAuthorities());
return authorities;
} else {
return Collections.emptyList();
}
}
}
Кроме того, мы должны настроить Пользовательский поставщик аутентификации SAML в качестве компонента в классе SecurityConfig :
@Bean
public SAMLAuthenticationProvider samlAuthenticationProvider() {
return new CustomSAMLAuthenticationProvider();
}5.8. Конфигурация безопасности
Наконец, мы настроим базовую безопасность HTTP с помощью уже обсужденных samlEntryPoint и samlFilter :
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.httpBasic().authenticationEntryPoint(samlEntryPoint);
http
.addFilterBefore(metadataGeneratorFilter(), ChannelProcessingFilter.class)
.addFilterAfter(samlFilter(), BasicAuthenticationFilter.class)
.addFilterBefore(samlFilter(), CsrfFilter.class);
http
.authorizeRequests()
.antMatchers("/").permitAll()
.anyRequest().authenticated();
http
.logout()
.addLogoutHandler((request, response, authentication) -> {
response.sendRedirect("/saml/logout");
});
}Вуаля! Мы завершили настройку SAML Spring Security, которая позволяет пользователю войти в IdP, а затем получить данные аутентификации пользователя в формате XML от IdP. Наконец, он аутентифицирует токен пользователя, чтобы разрешить доступ к нашему веб-приложению.
6. Домашний контролер
Теперь, когда наши конфигурации SAML Spring Security готовы вместе с настройкой учетной записи разработчика Okta, мы можем настроить простой контроллер для обеспечения целевой страницы и домашней страницы .
6.1. Отображение индекса и аутентификации
Во-первых, давайте добавим сопоставления к целевому URL-адресу по умолчанию (/) и/|/auth URI:
@RequestMapping("/")
public String index() {
return "index";
}
@GetMapping(value = "/auth")
public String handleSamlAuth() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) {
return "redirect:/home";
} else {
return "/";
}
}Затем мы добавим простой index.html это позволяет пользователю перенаправить аутентификацию Okta SAML с помощью ссылки login :
Baeldung Spring Security SAML Welcome to Baeldung Spring Security SAML
Login
Теперь мы готовы запустить наше приложение Spring Boot и получить к нему доступ по адресу http://localhost:8080/ :
Страница входа в систему Okta должна открываться при нажатии на Авторизоваться ссылка:
6.2. Домашняя Страница
Затем давайте добавим сопоставление с URL-адресом /home , чтобы перенаправить пользователя при успешной аутентификации:
@RequestMapping("/home")
public String home(Model model) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
model.addAttribute("username", authentication.getPrincipal());
return "home";
}Кроме того, мы добавим home.html для отображения вошедшего пользователя и ссылки на выход из системы:
Baeldung Spring Security SAML: Home Welcome!
You are successfully logged in!You are logged as null.
Logout
После успешного входа в систему мы должны увидеть домашнюю страницу:
7. Заключение
В этом уроке мы обсудили интеграцию Spring Security SAML с Okta.
Во-первых, мы создали учетную запись разработчика Okta с веб-интеграцией SAML 2.0. Затем мы создали проект Spring Boot с необходимыми зависимостями Maven.
Затем мы выполнили все необходимые настройки для SAML Spring Security, такие как samlEntryPoint , ssmlfilter , обработка метаданных и процессор SAML .
Наконец, мы создали контроллер и несколько страниц, таких как index и home , чтобы проверить нашу интеграцию SAML с Okta.
Как обычно, исходный код доступен на GitHub .