У меня есть веб-приложение с Spring-boot 2.0.1, защищенное Spring-Security. Я использую PersistentTokenRepository для Remember-Me и храню токены в базе данных MySQL.
В лог-файлах сервера я вижу довольно много трассировок стека с CookieTheftException. Их так много, что мне трудно поверить, что настоящие файлы cookie украдены, но предполагаю некоторую неправильную конфигурацию. От добавления некоторого анализирующего кода, похоже, затронуты только мобильные браузеры.
Servlet.service() for servlet [dispatcherServlet] in context with path [/r] threw exception
org.springframework.security.web.authentication.rememberme.CookieTheftException: Invalid remember-me token (Series/token) mismatch. Implies previous cookie theft attack.
at org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices.processAutoLoginCookie(PersistentTokenBasedRememberMeServices.java:119) ~[spring-security-web-5.0.4.RELEASE.jar!/:5.0.4.RELEASE]
Ручное тестирование не смогло воспроизвести это. Удаление файла cookie сеанса, но сохранение файла cookie с функцией запоминания и отправка запроса на ограниченный URL-адрес приводит к нормальному сеансу с проверкой подлинности.
Вот соответствующие части моей конфигурации безопасности:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig {
@Configuration
public static class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private RememberMeServices rememberMeServices;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.rememberMe()
.key(rememberMeKey)
.rememberMeServices(rememberMeServices);
;
}
}
/**
* Key for RememberMeServices and RememberMeAuthenticationProvider.
*/
private static final String rememberMeKey = "...";
@Bean
public RememberMeServices rememberMeServices(UserDetailsService userDetailsService, PersistentTokenRepository persistentTokenRepository) {
PersistentTokenBasedRememberMeServices rememberMeServices = new AnalyzingPersistentTokenBasedRememberMeServices(
rememberMeKey, userDetailsService, persistentTokenRepository);
rememberMeServices.setTokenValiditySeconds((int) Duration.of(366L, ChronoUnit.DAYS).toSeconds());
return rememberMeServices;
}
@Bean
public PersistentTokenRepository persistentTokenRepository(JdbcTemplate jdbcTemplate) {
JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
tokenRepository.setJdbcTemplate(jdbcTemplate);
return tokenRepository;
}
}
Этот AnalyzingPersistentTokenBasedRememberMeServices - это PersistentTokenBasedRememberMeServices с некоторой дополнительной записью в processAutoLoginCookie.
Еще одна особенность заключается в том, что я использую собственный AuthenticationProvider и предоставляю UserDetailsService только для RememberMe. Но, как было сказано выше, ручное тестирование работает нормально. Тем не менее, пользователи сообщают, что они слишком часто выходят из системы (тайм-аут сеанса составляет 24 часа).
Кто-нибудь испытал что-то подобное и нашел решение? Я пропустил какую-то важную конфигурацию?




PersistentTokenBasedRememberMeServices не подходит для приложений с параллельными запросами, которые могут отправлять одинаковые серии токенов.
См. Эти почти пятилетние и нерешенные отчеты об ошибках:
https://github.com/spring-projects/spring-security/issues/2648
https://github.com/spring-projects/spring-security/issues/3079
При использовании TokenBasedRememberMeServices таких проблем не возникает.
В итоге я использовал Redisson для реализации распределенной блокировки и распределенной карты для хранения быстро истекающего кеша, позволяя первому поступившему запросу пакета одновременных запросов обновлять токен и позволяя последующим запросам знать, что токен был недавно обновлен и использует это новое значение.
Проблема с реализацией Spring Security Web 4.2.13 в механизме пролонгации токенов
protected UserDetails processAutoLoginCookie(String[] cookieTokens,...
.....
PersistentRememberMeToken newToken = new PersistentRememberMeToken(
token.getUsername(), token.getSeries(), generateTokenData(), new Date());
try {
tokenRepository.updateToken(newToken.getSeries(), newToken.getTokenValue(),
newToken.getDate());
addCookie(newToken, request, response);
}
catch (Exception e) {
logger.error("Failed to update token: ", e);
throw new RememberMeAuthenticationException(
"Autologin failed due to data access problem");
}
При обновлении в случае одновременного запроса значение токена для одной и той же серии может быть несколько раз переписано в БД, но в cookie может быть сохранено другое значение.
Создание токена во время входа в систему работает хорошо, в худшем случае у вас может быть много постоянных токенов в БД, но один из них будет работать =)
В моем проекте исправлена ошибка путем модификации алгоритма обновления:
if (token.date + 1 < new Date()){
try {
PersistentRememberMeToken newToken = new PersistentRememberMeToken( token.getUsername(), generateSeriesData(), generateTokenData(), new Date())
tokenRepository.createNewToken(newToken)
addCookie(newToken, request, response)
}
catch (Exception e) {
logger.error("Failed to update token: ", e);
throw new RememberMeAuthenticationException(
"Autologin failed due to data access problem");
}
}
Этот код создает много записей БД в результате, но работает
Этот ответ немного вводит в заблуждение. В большинстве случаев люди выбирали PersistentTokenBasedRememberMeServices, потому что TokenBasedRememberMeServices изначально не подходили. А именно это не так безопасно.