Введение

Spring Security — это мощная платформа, обеспечивающая аутентификацию, авторизацию и другие функции безопасности для корпоративных приложений. В этой статье мы рассмотрим, как настроить Basic Authentication в приложении Spring Boot, используя более новый и гибкий подход SecurityFilterChain, и как мы можем настроить процесс аутентификации.

Основные сведения о базовой аутентификации

Прежде чем мы углубимся в кодирование, важно понять, что такое базовая аутентификация и как она работает.

Базовая аутентификация — это простая схема аутентификации, встроенная в протокол HTTP. Это метод, разработанный для того, чтобы клиент (обычно веб-браузер) предоставлял имя пользователя и пароль при запросе. Он называется базовым, потому что это один из самых простых способов аутентификации клиента на сервере.

Процесс Basic Authentication работает следующим образом:

  1. Клиент отправляет запрос GET на сервер.
  2. Если серверу требуется аутентификация, он отвечает кодом состояния 401 Unauthorized и включает поле заголовка WWW-Authenticate.
  3. Получив ответ 401, клиент отправляет еще один запрос GET, на этот раз с полем заголовка Авторизация, содержащим слово 'Basic', за которым следует пробел и base64. закодированная строка'имя пользователя:пароль'.
  4. Затем сервер декодирует строку base64 и, если имя пользователя и пароль совпадают, обрабатывает запрос клиента. Если они не совпадают, он может вернуть код состояния 401 Unauthorized, и процесс повторяется.

Вот пример того, как может выглядеть поле заголовка Авторизация:

Авторизация: базовая dXNlcjpwYXNzd29yZA==

В этом примере ‘dXNlcjpwYXNzd29yZA==’ — это base64 версия ‘user:password’.

Важно отметить, что Basic Authentication передает учетные данные в виде открытого текста (в кодировке base64, но не в зашифрованном виде), поэтому используйте его через HTTPS, чтобы защитить имя пользователя и пароль от перехвата.

Теперь, когда мы понимаем, как работает базовая аутентификация, давайте реализуем ее с помощью Spring Security.

Настройка конфигурации безопасности

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

Начнем с основной Конфигурации безопасности:

@Configuration
class SecurityConfig(
    private val authenticationEntryPoint: MyBasicAuthenticationEntryPoint
) {
    @Bean
    fun filterChain(http: HttpSecurity): SecurityFilterChain {
        http.csrf().disable()
            .authorizeHttpRequests { authorize ->
                authorize
                    .antMatchers("/public/**").permitAll()  // permit all access to /public
                    .anyRequest().authenticated()
            }
            .httpBasic {
                it.authenticationEntryPoint(authenticationEntryPoint)
            }
        return http.build()
    }
}

Здесь у нас есть один класс конфигурации Spring Security (SecurityConfig). Компонент SecurityFilterChain (filterChain) — это место, где происходит основная настройка безопасности.

Мы отключаем защиту CSRF, так как она подходит для REST API.

Теперь обратимся к правилам авторизации: любой запрос к «/public/**» разрешен без аутентификации, а любой другой запрос требует аутентификации.

Конфигурация httpBasic() задает нашу пользовательскую MyBasicAuthenticationEntryPoint в качестве точки входа для обработки ошибок аутентификации. Он внедряется в класс конфигурации через конструктор.

Служба сведений о пользователе

Далее мы определяем, как загружаются пользователи и как хранится их пароль:

@Bean
fun userDetailsService(): UserDetailsService {
    return InMemoryUserDetailsManager().apply {
        createUser(
            User.withUsername("user")
                .password(passwordEncoder().encode("password"))
                .roles("USER")
                .build()
        )
    }
}

@Bean
fun passwordEncoder() = BCryptPasswordEncoder()

Метод userDetailsService создает InMemoryUserDetailsManager и добавляет пользователя с именем пользователя 'user', паролем'password' и ролью 'USER'. пароль зашифрован с помощью BCrypt. Мы не рекомендуем его для производственных приложений, но он подходит для этой демонстрации.

Настройка точки входа для аутентификации

Мы также хотим настроить, как наше приложение обрабатывает ошибки аутентификации:

@Component
class MyBasicAuthenticationEntryPoint(
    private val objectMapper: ObjectMapper
) : BasicAuthenticationEntryPoint() {
    override fun commence(
        request: HttpServletRequest,
        response: HttpServletResponse,
        authEx: AuthenticationException
    ) {
        response.addHeader("WWW-Authenticate", "Basic realm=$realmName")
        response.status = HttpServletResponse.SC_UNAUTHORIZED
        response.contentType = "application/json;charset=UTF-8"
        val errorDetails: Map<String, String> = mapOf(
            "error" to "Authentication Failed",
            "message" to (authEx.message ?: "Unauthorized")
        )
        response.writer.println(objectMapper.writeValueAsString(errorDetails))
    }

    override fun afterPropertiesSet() {
        realmName = "Hyperskill"
        super.afterPropertiesSet()
    }
}

MyBasicAuthenticationEntryPoint — это настраиваемая точка входа, расширяющая BasicAuthenticationEntryPoint. В методе begin мы модифицируем ответ, включив в него заголовок WWW-Authenticate, установим статус 401 (Unauthorized) и установим тип содержимого "application/json;charset=UTF -8”. Затем мы создаем тело ответа JSON, которое включает сообщение об ошибке из исключения аутентификации.

Общедоступная конечная точка

Наконец, у нас есть простой контроллер, доступный для всех пользователей:

@RestController
@RequestMapping("/public")
class PublicController {
    @GetMapping
    fun getPublic() = "Endpoint for all"
}

Этот PublicController обрабатывает запросы GET к /public. Эта конечная точка доступна для всех пользователей, даже если они не прошли проверку подлинности, благодаря нашей конфигурации безопасности.

Примеры и тестирование

После того, как мы внедрили нашу конфигурацию безопасности, важно протестировать различные сценарии, чтобы убедиться, что она работает должным образом. Здесь мы рассмотрим два примера: один успешный публичный доступ и один неудачный аутентифицированный доступ.

1. Доступ к общедоступной конечной точке

Для общедоступной конечной точки, такой как "/public", все пользователи могут получить доступ к ресурсам, независимо от того, прошли ли они проверку подлинности или нет. В нашем случае, если на эту конечную точку будет отправлен запрос GET, ответ будет таким:

Endpoint for all

Это связано с тем, что мы настроили наше приложение для разрешения всех запросов к /public/** в нашей SecurityConfig.

2. Неудачная попытка аутентификации

Если пользователь, не прошедший проверку подлинности, попытается получить доступ к защищенной конечной точке, сработает наша пользовательская BasicAuthenticationEntryPoint, и пользователь получит ответ, аналогичный следующему:

{
  "error": "Authentication Failed",
  "message": "Full authentication is required to access this resource"
}

Этот ответ JSON включает сообщение об ошибке и дополнительные сведения об ошибке. Это происходит из нашей пользовательской реализации в методе MyBasicAuthenticationEntryPoint.commence(), где мы создаем подробный ответ об ошибке для сбоев аутентификации.

Эти примеры демонстрируют, как наша конфигурация безопасности обрабатывает различные сценарии. Следует проводить регулярное тестирование, чтобы гарантировать, что обновления или дополнения к приложению не изменят непреднамеренно это поведение. Надлежащие модульные и интеграционные тесты необходимы для обеспечения безопасности вашего приложения.

Подведение итогов

В этом руководстве показано, как настроить базовую аутентификацию в приложении Spring Boot с помощью Spring Security. Мы обсудили, как настроить параметры безопасности, загрузить сведения о пользователе и сохранить пароли, а также настроить точку входа аутентификации для обработки ошибок аутентификации.

Spring Security — это универсальный инструмент, который легко справляется со сложными требованиями безопасности. Он достаточно гибкий, чтобы удовлетворить большинство потребностей в защите приложений на основе Spring, и его использование может привести к созданию более чистого и удобного в сопровождении кода. Мы только коснулись того, что вы можете делать с помощью Spring Security, и я призываю вас погрузиться глубже и изучить другие его функции.

Продолжая осваивать Spring Security, рассмотрите дополнительные учебные ресурсы. Гиперскилл предлагает несколько актуальных тем, которые хорошо сочетаются с этим руководством:

Начало работы с Spring Security. В этом разделе представлено отличное введение в Spring Security. В нем объясняются основные концепции и проводится настройка Spring Security в новом проекте.

Spring Security Crypto: в этом разделе подробно описывается поддержка криптографии в Spring Security. Это может помочь вам понять, как Spring Security защищает пароли и конфиденциальные данные.

Аутентификация. В этом разделе подробно рассматривается процесс аутентификации, объясняется, как идентифицируются пользователи и проверяются их учетные данные. Он охватывает различные типы аутентификации и то, как они обрабатываются в Spring Security.

Каждый из этих разделов даст вам более глубокое понимание того, как работает Spring Security и как использовать его для защиты ваших приложений.

Приятного обучения!