Я пишу простой 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);
}
Пожалуйста, дайте мне знать, чего не хватает в моей реализации
Я имею в виду этот простой тест, который вы пытаетесь выполнить. Вы используете только 1 набор учетных данных, но создаете собственный AuthenticationProvider
вместо использования стандартного поставщика аутентификации в памяти. Нет необходимости предоставлять все учетные данные для одного теста. Я не утверждаю, что ваш подход неверен, но вам лучше знать все тонкости Spring Security и то, как все компоненты работают вместе, если вы откажетесь от того, что предоставляется из коробки.
Я разработал этот простой тест с намерением использовать его в нашей системе, где мы должны использовать стороннюю систему аутентификации. Для этого нам нужно подключить провайдера аутентификации. Поэтому, даже если эта проблема будет решена с помощью InMemoryUserDetailsManager, это не поможет в долгосрочной перспективе.
Как упоминалось в комментариях, вместо предоставления пользовательского 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, мне приходится вызывать эту стороннюю систему для аутентификации запроса, поэтому ваш ответ или ответ, предложенный Нико Ван Белль, мне не подходит. Я должен использовать поставщика аутентификации
Извините, это не было ясно на 100% и не было подробно описано в вашем исходном сообщении. Если вы посмотрите на DaoAuthenticationProvider
и базовый класс, вы увидите множество проблем, которые вы должны учитывать при реализации поставщика аутентификации имени пользователя/пароля, независимо от того, сторонний он или нет. Одним из примеров является атака по времени на пароли для поиска несуществующих пользователей. Другой — стирание учетных данных после завершения аутентификации (успешно или неудачно). Третьим будет обновление схем кодирования паролей. Список можно продолжить. На этом этапе вы можете настоятельно рассмотреть возможность использования OAuth.
Как предлагается в комментариях и ответах, даже если вы используете InMemoryUserDetailsManager
, проблема не решается, что означает, что после аутентификации пользователя с правильным именем пользователя и паролем его пароль не проверяется в последующих вызовах REST API, т.е. можно использовать любой пароль. Это связано с функциональностью класса BasicAuthenticationFilter
, которая пропускает пользователей, у которых есть действительный файл cookie JSESSION.
Чтобы решить эту проблему, мы должны настроить http для создания сеансов без состояния через
http .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
в configure
функции WebSecurityConfigurerAdapter
Проблема с InMemoryUserDetailManager заключается в том, что мы должны указать все имена пользователей и пароли. В моем случае аутентификация пользователя должна выполняться отдельной сторонней системой.