Введение

Единый вход (SSO) — это процесс аутентификации, который позволяет пользователям получать доступ к нескольким приложениям, используя единый набор учетных данных для входа. Это повышает безопасность и удобство работы пользователей, упрощая управление несколькими учетными записями в различных службах. Эта статья проведет вас через процесс создания многопользовательской корпоративной SSO-интеграции с использованием Spring Boot, которая может интегрироваться с любым поставщиком SSO, таким как Google, Outlook и другими, для разных клиентов. Мы предоставим подробные пояснения и примеры кода для лучшего понимания процесса.

Предпосылки

Прежде чем мы углубимся, убедитесь, что у вас установлено и настроено следующее:

  1. Комплект для разработки Java (JDK) 8 или более поздней версии
  2. Апач Мавен
  3. Текстовый редактор или интегрированная среда разработки (IDE) по вашему выбору.
  4. Базовое понимание Spring Boot, OAuth 2.0 и OpenID Connect (OIDC)

Начало работы с Spring Boot

  1. Создайте новый проект Spring Boot с помощью Spring Initializr. Выберите следующие зависимости:
  • Веб: «Весенняя паутина»
  • Безопасность: «Весенняя безопасность»
  • Клиент OAuth 2.0: «Клиент Spring Boot OAuth2»

2. Загрузите и извлеките сгенерированный проект.

3. Откройте проект в предпочтительной среде IDE и убедитесь, что все зависимости правильно загружены.

Внедрение SSO-интеграции

Мы будем использовать OAuth 2.0 и OpenID Connect (OIDC) для интеграции с внешними поставщиками SSO. OIDC — это уровень идентификации, созданный поверх OAuth 2.0, который обеспечивает функции аутентификации и авторизации. Выполните следующие действия, чтобы реализовать интеграцию SSO с несколькими арендаторами.

  1. Обновите файл application.properties:
# Base properties for SSO integration
spring.security.oauth2.client.registration.dynamic.registration-id=<your_registration_id>
spring.security.oauth2.client.registration.dynamic.scope=openid,email,profile
# These properties will be dynamically overridden for each tenant
spring.security.oauth2.client.registration.dynamic.client-id=<your_client_id>
spring.security.oauth2.client.registration.dynamic.client-secret=<your_client_secret>
spring.security.oauth2.client.registration.dynamic.provider=<your_sso_provider>

Замените <your_registration_id>, <your_client_id>, <your_client_secret> и <your_sso_provider> фактическими значениями, полученными от поставщика единого входа.

2. Создайте новый класс DynamicOAuth2ClientRegistration для хранения сведений о динамической регистрации клиента:

package com.example.sso;

import org.springframework.security.oauth2.client.registration.ClientRegistration;
public class DynamicOAuth2ClientRegistration {
    private String tenantId;
    private ClientRegistration clientRegistration;
    // Constructor, getters, and setters
}

3. Создайте новый класс DynamicOAuth2ClientRegistrationRepository, реализующий ClientRegistrationRepository:

package com.example.sso;

import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import java.util.HashMap;
import java.util.Map;
public class DynamicOAuth2ClientRegistrationRepository implements ClientRegistrationRepository {
    private final Map<String, DynamicOAuth2ClientRegistration> dynamicClientRegistrations;
    public DynamicOAuth2ClientRegistrationRepository() {
        this.dynamicClientRegistrations = new HashMap<>();
    }
    @Override
    public ClientRegistration findByRegistrationId(String registrationId) {
        // Retrieve the client registration for the given tenant
        DynamicOAuth2ClientRegistration dynamicRegistration = dynamicClientRegistrations.get(registrationId);
        return dynamicRegistration == null ? null : dynamicRegistration.getClientRegistration();
    }
    // Additional methods for managing dynamic client registrations
}

4. Измените класс WebSecurityConfig, чтобы настроить bean-компонент DynamicOAuth2ClientRegistrationRepository и внедрить его в конфигурацию HttpSecurity:

package com.example.sso;

// ...other imports...
import org.springframework.context.annotation.Bean;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    public DynamicOAuth2ClientRegistrationRepository dynamicClientRegistrationRepository() {
        return new DynamicOAuth2ClientRegistrationRepository();
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                // ...existing code...
            .and()
                .oauth2Login()
                .clientRegistrationRepository(dynamicClientRegistrationRepository())
                .defaultSuccessURL("/user", true);
    }
}

5. Создайте новый класс Tenant для представления информации о каждом арендаторе:

package com.example.sso;

public class Tenant {
    private String id;
    private String name;
    private String ssoProvider;
    private String clientId;
    private String clientSecret;
    // Constructor, getters, and setters
}

6. Добавьте новый класс TenantRepository для хранения информации об арендаторах и управления ею:

package com.example.sso;

import java.util.List;
public interface TenantRepository {
    Tenant findById(String id);
    List<Tenant> findAll();
    Tenant save(Tenant tenant);
    void deleteById(String id);
}

Вы можете реализовать TenantRepository, используя предпочтительную технологию базы данных (например, JPA, JDBC, MongoDB и т. д.). В этой статье не рассматриваются конкретные детали реализации базы данных, но вы можете найти дополнительную информацию в Документации Spring Data.

7. Создайте новый класс TenantService для обработки логики конкретного арендатора:

package com.example.sso;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class TenantService {
    @Autowired
    private TenantRepository tenantRepository;
    public Tenant findById(String id) {
        return tenantRepository.findById(id);
    }
    public List<Tenant> findAll() {
        return tenantRepository.findAll();
    }
    public Tenant save(Tenant tenant) {
        return tenantRepository.save(tenant);
    }
    public void deleteById(String id) {
        tenantRepository.deleteById(id);
    }
}

8. Обновите класс DynamicOAuth2ClientRegistrationRepository, чтобы внедрить TenantService и получить сведения о регистрации клиента для конкретного арендатора:

package com.example.sso;

// ...other imports...
import org.springframework.beans.factory.annotation.Autowired;
public class DynamicOAuth2ClientRegistrationRepository implements ClientRegistrationRepository {
    // ...existing code...
    @Autowired
    private TenantService tenantService;
    @Override
    public ClientRegistration findByRegistrationId(String registrationId) {
        Tenant tenant = tenantService.findById(registrationId);
        if (tenant == null) {
            return null;
        }
        // Create a dynamic client registration based on the tenant's SSO provider
        DynamicOAuth2ClientRegistration dynamicRegistration = createDynamicClientRegistration(tenant);
        return dynamicRegistration == null ? null : dynamicRegistration.getClientRegistration();
    }
    // ...other methods...
    private DynamicOAuth2ClientRegistration createDynamicClientRegistration(Tenant tenant) {
        // Retrieve the OAuth2 provider details based on the tenant's SSO provider
        // and create a dynamic client registration using the tenant's client ID and secret
    }
}

9. Обновите класс WebSecurityConfig, чтобы настроить bean-компонент DynamicOAuth2ClientRegistrationRepository и внедрить его в конфигурацию HttpSecurity:

package com.example.sso;

// ...other imports...

import org.springframework.context.annotation.Bean;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public DynamicOAuth2ClientRegistrationRepository dynamicClientRegistrationRepository() {
        return new DynamicOAuth2ClientRegistrationRepository();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                // ...existing code...
            .and()
                .oauth2Login()
                .clientRegistrationRepository(dynamicClientRegistrationRepository())
                .defaultSuccessURL("/user", true);
    }
}

Теперь ваше приложение готово к интеграции многопользовательской системы единого входа с различными поставщиками. Вы можете хранить поставщика единого входа для каждого арендатора, идентификатор клиента и секрет клиента в базе данных и динамически извлекать сведения о регистрации клиента на основе текущего арендатора.

Тестирование мультитенантной интеграции единого входа

Чтобы протестировать интеграцию SSO с несколькими арендаторами, вы можете создать простое веб-приложение со страницей входа, которая отображает доступных поставщиков SSO для каждого арендатора. Выполните следующие шаги:

  1. Обновите файл application.properties, чтобы включить зависимость Thymeleaf:
spring.thymeleaf.cache=false

2. Добавьте зависимость Thymeleaf к вашему pom.xml:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

3. Создайте новый класс контроллера LoginController:

package com.example.sso;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@Controller
public class LoginController {

    @Autowired
    private TenantService tenantService;

    @GetMapping("/login/{tenantId}")
    public String login(@PathVariable("tenantId") String tenantId, Model model) {
        Tenant tenant = tenantService.findById(tenantId);

        if (tenant == null) {
            return "error";
        }

        model.addAttribute("tenant", tenant);
        return "login";
    }
}

4. Создайте новый шаблон Thymeleaf src/main/resources/templates/login.html:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Login</title>
</head>
<body>
    <h1>Login with SSO Provider</h1>
    <div th:if="${tenant.ssoProvider == 'google'}">
        <a th:href="@{/oauth2/authorization/{tenantId}(tenantId=${tenant.id})}">Login with Google</a>
    </div>
    <div th:if="${tenant.ssoProvider == 'outlook'}">
        <a th:href="@{/oauth2/authorization/{tenantId}(tenantId=${tenant.id})}">Login with Outlook</a>
    </div>
    <!-- Add other SSO providers as needed -->
</body>
</html>

Теперь вы можете запустить свое приложение и протестировать интеграцию SSO с несколькими арендаторами, посетив URL-адрес /login/{tenantId} с соответствующим идентификатором арендатора. После успешной аутентификации пользователь будет перенаправлен на URL-адрес /user.

Заключение

В этой статье мы продемонстрировали, как создать мультитенантную интеграцию SSO предприятия с Spring Boot, которая может интегрироваться с различными поставщиками SSO, такими как Google, Outlook, Okta и другими. Реализация использует OAuth 2.0 и OpenID Connect (OIDC) для обеспечения функций аутентификации и авторизации. Мы рассмотрели необходимую конфигурацию, дизайн базы данных.

🔗 Свяжитесь со мной в LinkedIn!

Я надеюсь, что вы нашли эту статью полезной! Если вы хотите узнать больше и быть в курсе моих последних идей и статей, не стесняйтесь связаться со мной в LinkedIn.

Давайте расширять наши сети, участвовать в содержательных дискуссиях и делиться своим опытом в мире разработки программного обеспечения и за его пределами. С нетерпением ждем связи с вами! 😊

Подпишитесь на меня в LinkedIn ➡️