Ответ 1
Есть две проблемы с настройкой функций RememberMe с LDAP:
- выбор правильной реализации RememberMe (токены против PersistentTokens)
- его конфигурация с помощью Spring Конфигурация Java
Я сделаю это шаг за шагом.
Функция аутентификации на основе токена (TokenBasedRememberMeServices
) работает во время аутентификации следующим образом:
- пользователь получает аутентификацию (agaisnt AD), и мы в настоящее время знаем идентификатор пользователя и пароль.
- мы строим значение username + expirationTime + password + staticKey и создаем хэш MD5 из него
- мы создаем файл cookie, который содержит имя пользователя + истечение + вычисленный хэш
Когда пользователь хочет вернуться к службе и пройти аутентификацию с помощью функции "запомнить меня", мы:
- проверить, существует ли файл cookie и не истекает.
- введите идентификатор пользователя из файла cookie и вызовите предоставленный UserDetailsService, который, как ожидается, вернет информацию, относящуюся к идентификатору пользователя, , включая пароль
- Затем мы вычисляем хэш из возвращаемых данных и проверяем, что хэш в файле cookie совпадает со значением, которое мы вычислили
- если он соответствует, мы возвращаем объект аутентификации пользователя
Процесс проверки хэша требуется, чтобы убедиться, что никто не может создать "фальшивый" файл cookie, который позволит им олицетворять другого пользователя. Проблема заключается в том, что этот процесс зависит от возможности загрузки пароля из нашего репозитория - но это невозможно в Active Directory - мы не можем загружать пароль открытого текста на основе имени пользователя.
Это делает реализацию на основе Token непригодной для использования с AD (если мы не начнем создавать локальное хранилище пользователей, которое содержит пароль или некоторые секретные пользовательские учетные данные, и я не предлагаю этот подход, поскольку я не знаю другие детали вашего приложения, хотя это может быть хорошим способом).
Другое помните, что реализация на основе постоянных токенов (PersistentTokenBasedRememberMeServices
) и работает как это (немного упрощенным способом):
- Когда пользователь аутентифицируется, мы генерируем случайный токен
- мы сохраняем токен в хранилище вместе с информацией об ассоциированном с ним идентификаторе пользователя
- мы создаем файл cookie, который включает идентификатор маркера
Когда пользователь хочет аутентифицировать нас:
- проверьте, есть ли у нас файл cookie с идентификатором токена.
- проверить, существует ли идентификатор маркера в базе данных
- Загрузка пользовательских данных на основе информации в базе данных
Как вы можете видеть, пароль больше не требуется, хотя теперь нам нужно хранить токены (обычно это база данных, которую можно использовать в памяти для тестирования), которая используется вместо проверки пароля.
И это приводит нас к части конфигурации. Основная конфигурация для запоминающего устройства, основанного на постоянном токене, выглядит следующим образом:
@Override
protected void configure(HttpSecurity http) throws Exception {
....
String internalSecretKey = "internalSecretKey";
http.rememberMe().rememberMeServices(rememberMeServices(internalSecretKey)).key(internalSecretKey);
}
@Bean
public RememberMeServices rememberMeServices(String internalSecretKey) {
BasicRememberMeUserDetailsService rememberMeUserDetailsService = new BasicRememberMeUserDetailsService();
InMemoryTokenRepositoryImpl rememberMeTokenRepository = new InMemoryTokenRepositoryImpl();
PersistentTokenBasedRememberMeServices services = new PersistentTokenBasedRememberMeServices(staticKey, rememberMeUserDetailsService, rememberMeTokenRepository);
services.setAlwaysRemember(true);
return services;
}
В этой реализации будет использоваться хранилище токенов в памяти, которое должно быть заменено на JdbcTokenRepositoryImpl
для производства. Предоставленный UserDetailsService
отвечает за загрузку дополнительных данных для пользователя, идентифицированного идентификатором пользователя, загруженным из файла cookie "помнить меня". Простейшая реализация может выглядеть так:
public class BasicRememberMeUserDetailsService implements UserDetailsService {
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return new User(username, "", Collections.<GrantedAuthority>emptyList());
}
}
Вы также можете предоставить другую реализацию UserDetailsService
, которая загружает дополнительные атрибуты или членство в группах из вашего AD или внутренней базы данных в зависимости от ваших потребностей. Это может выглядеть так:
@Bean
public RememberMeServices rememberMeServices(String internalSecretKey) {
LdapContextSource ldapContext = getLdapContext();
String searchBase = "OU=Users,DC=test,DC=company,DC=com";
String searchFilter = "(&(objectClass=user)(sAMAccountName={0}))";
FilterBasedLdapUserSearch search = new FilterBasedLdapUserSearch(searchBase, searchFilter, ldapContext);
search.setSearchSubtree(true);
LdapUserDetailsService rememberMeUserDetailsService = new LdapUserDetailsService(search);
rememberMeUserDetailsService.setUserDetailsMapper(new CustomUserDetailsServiceImpl());
InMemoryTokenRepositoryImpl rememberMeTokenRepository = new InMemoryTokenRepositoryImpl();
PersistentTokenBasedRememberMeServices services = new PersistentTokenBasedRememberMeServices(internalSecretKey, rememberMeUserDetailsService, rememberMeTokenRepository);
services.setAlwaysRemember(true);
return services;
}
@Bean
public LdapContextSource getLdapContext() {
LdapContextSource source = new LdapContextSource();
source.setUserDn("[email protected]"+DOMAIN);
source.setPassword("password");
source.setUrl(URL);
return source;
}
Это поможет вам вспомнить функциональность, которая работает с LDAP и предоставляет загруженные данные внутри RememberMeAuthenticationToken
, которые будут доступны в SecurityContextHolder.getContext().getAuthentication()
. Он также сможет повторно использовать существующую логику для анализа данных LDAP в объекте User (CustomUserDetailsServiceImpl
).
Как отдельный вопрос, есть также одна проблема с кодом, размещенным в вопросе, вы должны заменить:
authManagerBuilder
.authenticationProvider(activeDirectoryLdapAuthenticationProvider())
.userDetailsService(userDetailsService())
;
с:
authManagerBuilder
.authenticationProvider(activeDirectoryLdapAuthenticationProvider())
;
Вызов функции userDetailsService должен выполняться только для добавления аутентификации на основе DAO (например, для базы данных) и должен быть вызван с реальной реализацией службы подробных сведений о пользователе. Ваша текущая конфигурация может привести к бесконечным циклам.