Рубрики
Без рубрики

Руководство по SAML с Spring Security

Автор оригинала: 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 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 .