Весенняя загрузка WebSecurityConfigurerAdapter, проблема проверки пароля пользователя с базовой аутентификацией

Я пишу простой REST API с использованием Spring Boot и хочу включить базовую аутентификацию. Поэтому я использовал WebSecurityConfigurerAdapter, как показано ниже. Для простоты я просто хочу проверить только пароль (pwd123) и разрешить вход любому пользователю. Пожалуйста, обратитесь к коду ниже.

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(new AuthenticationProvider() {
            @Override
            public Authentication authenticate(Authentication authentication) throws AuthenticationException {
                if (authentication == null || authentication.getCredentials() == null) {
                    throw new BadCredentialsException("Bad credentials");
                }
                if (authentication.getCredentials().equals("pwd123")) {
                    return new UsernamePasswordAuthenticationToken(authentication.getName(),
                            authentication.getCredentials().toString(),
                            Collections.emptyList());
                }

                return null;
            }

            @Override
            public boolean supports(Class<?> authentication) {
                return authentication.equals(UsernamePasswordAuthenticationToken.class);
            }
        });
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().authenticated()
                .and().httpBasic();
    }
}

Предположим, что user_A получил доступ к REST API с допустимым паролем, то есть pwd123, а затем выполните вызов API отправки с неправильным паролем. Однако пользователю разрешен доступ к API, что является проблемой.

Когда я выполняю отладку, я понял, что функция authenticationIsRequired в классе BasicAuthenticationFilter, который находится в Spring Security, в таком сценарии возвращает false. Пожалуйста, обратитесь к этому коду.

    private boolean authenticationIsRequired(String username) {
    // Only reauthenticate if username doesn't match SecurityContextHolder and user
    // isn't authenticated (see SEC-53)
    Authentication existingAuth = SecurityContextHolder.getContext().getAuthentication();
    if (existingAuth == null || !existingAuth.isAuthenticated()) {
        return true;
    }
    // Limit username comparison to providers which use usernames (ie
    // UsernamePasswordAuthenticationToken) (see SEC-348)
    if (existingAuth instanceof UsernamePasswordAuthenticationToken && !existingAuth.getName().equals(username)) {
        return true;
    }
    // Handle unusual condition where an AnonymousAuthenticationToken is already
    // present. This shouldn't happen very often, as BasicProcessingFitler is meant to
    // be earlier in the filter chain than AnonymousAuthenticationFilter.
    // Nevertheless, presence of both an AnonymousAuthenticationToken together with a
    // BASIC authentication request header should indicate reauthentication using the
    // BASIC protocol is desirable. This behaviour is also consistent with that
    // provided by form and digest, both of which force re-authentication if the
    // respective header is detected (and in doing so replace/ any existing
    // AnonymousAuthenticationToken). See SEC-610.
    return (existingAuth instanceof AnonymousAuthenticationToken);
}

Пожалуйста, дайте мне знать, чего не хватает в моей реализации

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

dGayand 06.05.2022 09:02

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

Nico Van Belle 06.05.2022 09:24

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

dGayand 06.05.2022 12:40
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
3
71
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Как упоминалось в комментариях, вместо предоставления пользовательского AuthenticationProvider вы можете попробовать создать собственный UserDetailsService. Вот полная конфигурация:

@EnableWebSecurity
public class SecurityConfiguration {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeRequests((authorizeRequests) -> authorizeRequests
                .anyRequest().authenticated()
            )
            .httpBasic(Customizer.withDefaults());

        return http.build();
    }

    @Bean
    public UserDetailsService userDetailsService() {
        return (username) -> new User(username, "{noop}pwd123", AuthorityUtils.createAuthorityList("ROLE_USER"));
    }

}

Когда вы переходите к поиску пользователя через стороннюю службу, вы можете добавить код для этого в пользовательскую UserDetailsService (лямбда-функция или фактический класс, реализующий интерфейс) и продолжать возвращать org.springframework.security.core.userdetails.User.

Примечание: На самом деле я не рекомендую использовать текстовые пароли в рабочей среде. Вы бы заменили {noop}pwd123 на что-то вроде {bcrypt}<bcrypt encoded password here>.

Как я упоминал в комментариях, аутентификация должна выполняться через стороннюю систему, что означает, что у меня нет доступа к пользователям или их паролям в системе. Когда вызывается REST Api, мне приходится вызывать эту стороннюю систему для аутентификации запроса, поэтому ваш ответ или ответ, предложенный Нико Ван Белль, мне не подходит. Я должен использовать поставщика аутентификации

dGayand 07.05.2022 04:28

Извините, это не было ясно на 100% и не было подробно описано в вашем исходном сообщении. Если вы посмотрите на DaoAuthenticationProvider и базовый класс, вы увидите множество проблем, которые вы должны учитывать при реализации поставщика аутентификации имени пользователя/пароля, независимо от того, сторонний он или нет. Одним из примеров является атака по времени на пароли для поиска несуществующих пользователей. Другой — стирание учетных данных после завершения аутентификации (успешно или неудачно). Третьим будет обновление схем кодирования паролей. Список можно продолжить. На этом этапе вы можете настоятельно рассмотреть возможность использования OAuth.

Steve Riesenberg 07.05.2022 07:09
Ответ принят как подходящий

Как предлагается в комментариях и ответах, даже если вы используете InMemoryUserDetailsManager, проблема не решается, что означает, что после аутентификации пользователя с правильным именем пользователя и паролем его пароль не проверяется в последующих вызовах REST API, т.е. можно использовать любой пароль. Это связано с функциональностью класса BasicAuthenticationFilter, которая пропускает пользователей, у которых есть действительный файл cookie JSESSION.

Чтобы решить эту проблему, мы должны настроить http для создания сеансов без состояния через http .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()

в configure функции WebSecurityConfigurerAdapter

См. Почему BasicAuthenticationFilter в весенней безопасности соответствует только имени пользователя, а не паролю

Другие вопросы по теме