В статье предлагается использовать SpringBoot для реализации контроля рисков безопасности во время автоматического входа в систему.

Узнали: SpringBoot сотрудничает с SpringSecurity для реализации функции автоматического входа в систему

Зная некоторые риски безопасности автоматического входа в Spring Boot в практических приложениях, мы должны минимизировать эти риски безопасности.Сегодня я расскажу вам о том, как снизить риски безопасности.

(1) Схема постоянного токена
(2) Вторичная проверка

1. Постоянный токен

1. Принцип

Чтобы понять постоянный токен, вы должны сначала понять основной игровой процесс автоматического входа в систему: SpringBoot взаимодействует с SpringSecurity для реализации функции автоматического входа в систему.

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

В постоянный токен добавляются два новых параметра проверки, вычисляемых хеш-функцией MD5: один - есть, seriesдругой - token. Среди них, seriesтолько когда пользователи входят в систему с использованием имени пользователя / пароля, будут сгенерированы или обновлены, но tokenпока есть новый сеанс, он будет регенерироваться, поэтому вы можете избежать многопользовательского пользователя при входе в систему как мобильный QQ, мобильный телефон Как только вы войдете в систему, он запустит логин другого мобильного телефона, так что пользователь легко узнает, произошла ли утечка учетной записи (я видел, как небольшой партнер в группе обмена Song Ge обсуждает, как запретить многопользовательский вход, на самом деле вы можете узнать из этой идеи здесь ).

Класс обработки постоянных токенов PersistentTokenBasedRememberMeServices, в последней статье, о которой мы говорили о конкретном процессе автоматического входа в систему, находится в классе TokenBasedRememberMeServices, у них есть общий родитель:

Вставьте описание изображения сюда
Класс обработки, используемый для сохранения токена PersistentRememberMeToken, это определение этого класса также очень краткая команда:

public class PersistentRememberMeToken {
    
    
 private final String username;
 private final String series;
 private final String tokenValue;
 private final Date date;
    //省略 getter
}

Дата здесь представляет время, когда в последний раз использовался автоматический вход.

2. Демонстрация кода

Далее я покажу вам конкретное использование постоянных токенов через код.

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

public class JdbcTokenRepositoryImpl extends JdbcDaoSupport implements
  PersistentTokenRepository {
    
    
 public static final String CREATE_TABLE_SQL = "create table persistent_logins (username varchar(64) not null, series varchar(64) primary key, "
   + "token varchar(64) not null, last_used timestamp not null)";
 public static final String DEF_TOKEN_BY_SERIES_SQL = "select username,series,token,last_used from persistent_logins where series = ?";
 public static final String DEF_INSERT_TOKEN_SQL = "insert into persistent_logins (username, series, token, last_used) values(?,?,?,?)";
 public static final String DEF_UPDATE_TOKEN_SQL = "update persistent_logins set token = ?, last_used = ? where series = ?";
 public static final String DEF_REMOVE_USER_TOKENS_SQL = "delete from persistent_logins where username = ?";
}

В соответствии с этим определением SQL мы можем проанализировать структуру таблицы. Вот сценарий SQL:

CREATE TABLE `persistent_logins` (
  `username` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL,
  `series` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL,
  `token` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL,
  `last_used` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`series`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

Сначала мы подготавливаем эту таблицу в базе данных: копируем скрипт и выполняем его напрямую.

Вставьте описание изображения сюда
Поскольку мы хотим подключиться к базе данных, нам также необходимо подготовить зависимости jdbc и mysql, как показано ниже:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

Затем измените application.properties, чтобы настроить информацию о подключении к базе данных:

spring.datasource.url=jdbc:mysql://localhost:3306/security?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root

Затем мы изменяем SecurityConfig следующим образом:

@Autowired
DataSource dataSource;
@Bean
JdbcTokenRepositoryImpl jdbcTokenRepository() {
    
    
    JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
    jdbcTokenRepository.setDataSource(dataSource);
    return jdbcTokenRepository;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
    
    
    http.authorizeRequests()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .and()
            .rememberMe()
            .key("yolo")
            .tokenRepository(jdbcTokenRepository())
            .and()
            .csrf().disable();
}

Предоставьте экземпляр JdbcTokenRepositoryImpl и настройте для него источник данных DataSource и, наконец, включите экземпляр JdbcTokenRepositoryImpl в конфигурацию через tokenRepository.

Хорошо, после всего этого мы можем протестировать.

3. Тестирование

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

Просмотреть токен "запомнить меня":

Вставьте описание изображения сюда
После анализа токена формат будет следующим:

@Test
void contextLoads() {
    
    
     String s = new String(
            Base64.getDecoder().decode("UE8yVWZveUxyQWxJZUJqSnNTT0I2USUzRCUzRDpQdGdHV1R5SHNWUXprdEoxNzBUNWdnJTNEJTNE"));
     System.out.println("s = " + s);
    }
PO2UfoyLrAlIeBjJsSOB6Q%3D%3D:PtgGWTyHsVQzktJ170T5gg%3D%3D

Среди них %3Dозначает =, поэтому приведенные выше символы фактически могут быть переведены в следующие:

PO2UfoyLrAlIeBjJsSOB6Q==:PtgGWTyHsVQzktJ170T5gg==

На этом этапе, просмотрев базу данных, мы обнаружили, что запись была создана в предыдущей таблице.

Вставьте описание изображения сюда
Записи в базе данных соответствуют токену "запомнить меня", который мы видели после синтаксического анализа.

4. Анализ исходного кода

Анализ исходного кода здесь в основном такой же, как и процесс в предыдущей статье, но класс реализации изменился, то есть изменилась реализация генерации токенов / токенов синтаксического анализа, поэтому здесь я в основном покажу вам различия, проблемы процесса, всех Вы можете обратиться к предыдущей статье.

На этот раз основным классом реализации является: PersistentTokenBasedRememberMeServices, давайте сначала рассмотрим несколько методов, связанных с генерацией токенов:

protected void onLoginSuccess(HttpServletRequest request,
  HttpServletResponse response, Authentication successfulAuthentication) {
    
    
 String username = successfulAuthentication.getName();
 PersistentRememberMeToken persistentToken = new PersistentRememberMeToken(
   username, generateSeriesData(), generateTokenData(), new Date());
 tokenRepository.createNewToken(persistentToken);
 addCookie(persistentToken, request, response);
}
protected String generateSeriesData() {
    
    
 byte[] newSeries = new byte[seriesLength];
 random.nextBytes(newSeries);
 return new String(Base64.getEncoder().encode(newSeries));
}
protected String generateTokenData() {
    
    
 byte[] newToken = new byte[tokenLength];
 random.nextBytes(newToken);
 return new String(Base64.getEncoder().encode(newToken));
}
private void addCookie(PersistentRememberMeToken token, HttpServletRequest request,
  HttpServletResponse response) {
    
    
 setCookie(new String[] {
    
     token.getSeries(), token.getTokenValue() },
   getTokenValiditySeconds(), request, response);
}

можно увидеть:

(1) После успешного входа в систему сначала получите имя пользователя, которое является именем пользователя.
(2) Затем, пример конфигурации PersistentRememberMeToken, generateSeriesDataи generateTokenDataметоды используются для получения, seriesи token, по сути, конкретный процесс SecureRandomгенерации вызова генерирует случайное число для повторного кодирования Base64, отличное от ранее использованного нами, Math.randomили используется java.util.Randomтакое псевдослучайное число, SecureRandom. Он похож на правила генерации случайных чисел в криптографии, и его выходные результаты труднее предсказать, они подходят для использования в таких сценариях, как вход в систему.
(3) вызывает метод tokenRepositoryэкземпляра createNewToken, tokenRepositoryфактически мы запускаем конфигурацию JdbcTokenRepositoryImpl, поэтому эта строка кода будет фактически PersistentRememberMeTokenсохранена в базе данных.
(4) Наконец addCookie, как видите, были добавлены серия и токен.

Это процесс генерации токена, а также процесс проверки токена. Также в этом классе есть метод processAutoLoginCookie:

protected UserDetails processAutoLoginCookie(String[] cookieTokens,
  HttpServletRequest request, HttpServletResponse response) {
    
    
 final String presentedSeries = cookieTokens[0];
 final String presentedToken = cookieTokens[1];
 PersistentRememberMeToken token = tokenRepository
   .getTokenForSeries(presentedSeries);
 if (!presentedToken.equals(token.getTokenValue())) {
    
    
  tokenRepository.removeUserTokens(token.getUsername());
  throw new CookieTheftException(
    messages.getMessage(
      "PersistentTokenBasedRememberMeServices.cookieStolen",
      "Invalid remember-me token (Series/token) mismatch. Implies previous cookie theft attack."));
 }
 if (token.getDate().getTime() + getTokenValiditySeconds() * 1000L < System
   .currentTimeMillis()) {
    
    
  throw new RememberMeAuthenticationException("Remember-me login has expired");
 }
 PersistentRememberMeToken newToken = new PersistentRememberMeToken(
   token.getUsername(), token.getSeries(), generateTokenData(), new Date());
 tokenRepository.updateToken(newToken.getSeries(), newToken.getTokenValue(),
    newToken.getDate());
 addCookie(newToken, request, response);
 return getUserDetailsService().loadUserByUsername(token.getUsername());
}

(1) Сначала проанализируйте серию и токен из файла cookie, отправленного из внешнего интерфейса.
(2) Запросите экземпляр PersistentRememberMeToken из базы данных в соответствии с серией.
(3) Если обнаруженный токен отличается от токена, отправленного из внешнего интерфейса, это означает, что учетная запись может быть украдена (после того, как кто-то другой войдет в систему с вашим токеном, токен изменится). В это время соответствующий токен удаляется в соответствии с именем пользователя, что эквивалентно повторному вводу имени пользователя и пароля для входа в систему для получения нового разрешения на автоматический вход.
(4) Затем проверьте, не истек ли срок действия токена.
(5) Создайте новый объект PersistentRememberMeToken и обновите токен в базе данных (об этом мы говорили в начале статьи, новый сеанс будет соответствовать новому токену).
(6) Повторно добавьте новый токен в файл cookie и вернитесь.
(7) Запросить информацию о пользователе на основе имени пользователя, а затем пройти процесс входа в систему.

Во-вторых, вторая проверка

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

Поэтому давайте посмотрим на другое решение - вторую проверку.

Вторую проверку реализовать немного сложнее, поэтому позвольте мне сначала поговорить об идеях.

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

Эта функция имеет более удобный фильтр для настройки в Shiro, и Spring Security, конечно же, такой же. Например, теперь я предоставляю три интерфейса доступа:

(1) Первый /helloинтерфейс после сертификации, если вы можете получить доступ либо через автоматическую аутентификацию, либо через аутентификацию при входе, пока вы можете получить доступ с помощью аутентификации по имени пользователя и паролю.
(2) Ко второму /adminинтерфейсу можно получить доступ после аутентификации по имени пользователя и паролю, если пользователь автоматически вошел в систему с помощью сертифицированного, вы должны повторно ввести свое имя пользователя и пароль для доступа к интерфейсу.
(3) Третий /remembermeинтерфейс для доступа должен быть через автоматическую аутентификацию входа, если пользователь является аутентификацией имени пользователя / пароля, вы не можете получить доступ к интерфейсу.

@Override
protected void configure(HttpSecurity http) throws Exception {
    
    
    http.authorizeRequests()
            .antMatchers("/rememberme").rememberMe()
            .antMatchers("/admin").fullyAuthenticated()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .and()
            .rememberMe()
            .key("javaboy")
            .tokenRepository(jdbcTokenRepository())
            .and()
            .csrf().disable();
}

(1) /remembermeинтерфейс требует rememberMeдоступа.
(2) /adminтребуется fullyAuthenticated, в fullyAuthenticatedотличие от authenticated, fullyAuthenticatedне содержит форму автоматического входа в систему и authenticatedвключает форму автоматического входа.
(3) Последний оставшийся интерфейс (/ hello) имеет authenticatedдоступ.

ОК, после завершения настройки перезапустите тест

Вставьте описание изображения сюда

рекомендация

отblog.csdn.net/nanhuaibeian/article/details/108763168