Spring Security автоматически удаляет предыдущего пользователя, вошедшего в систему, одна конфигурация выполнена!

Spring Boot
Spring Security автоматически удаляет предыдущего пользователя, вошедшего в систему, одна конфигурация выполнена!

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

После того, как я разработал его сам, я также столкнулся с точно такими же потребностями.Поскольку недавняя серия Spring Security сериализуется, я обсужу с вами, как реализовать эту функцию в сочетании со Spring Security.

Эта статья - тринадцатая статья в этой серии. Чтение предыдущих статей поможет вам лучше понять эту статью:

  1. Выкопайте большую яму и позвольте Spring Security сделать это!
  2. Song Ge поможет вам начать работу с Spring Security, не спрашивайте, как снова расшифровать пароль.
  3. Научите, как настроить вход в форму в Spring Security
  4. Spring Security разделяет внешний и внутренний интерфейсы, давайте не будем переходить по страницам! Все взаимодействия JSON
  5. Операции авторизации в Spring Security оказались такими простыми
  6. Как Spring Security хранит пользовательские данные в базе данных?
  7. Spring Security+Spring Data Jpa объединяют усилия, управление безопасностью становится еще проще!
  8. Spring Boot + Spring Security реализует функцию автоматического входа в систему
  9. Spring Boot автоматически входит в систему, как контролировать риски безопасности?
  10. В проекте микросервиса, где Spring Security лучше, чем Shiro?
  11. Два способа для Spring Security настроить логику аутентификации (расширенный игровой процесс)
  12. Как быстро просмотреть IP-адрес пользователя для входа в систему и другую информацию в Spring Security?

1. Анализ спроса

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

Чтобы понять, что пользователь не может войти на два устройства одновременно, у нас есть две идеи:

  • Позже логин автоматически кикнул перед логином, так как эффект мы видим в застегнутом.
  • Если пользователь уже вошел в систему, Latecomers не разрешается войти в систему.

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

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

2. Конкретная реализация

2.1 Запуск зарегистрированных пользователей

Чтобы удалить старый логин с новым логином, нам нужно только установить максимальное количество сеансов равным 1, и конфигурация выглядит следующим образом:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .loginPage("/login.html")
            .permitAll()
            .and()
            .csrf().disable()
            .sessionManagement()
            .maximumSessions(1);
}

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

После завершения настройки протестируйте Chrome и Firefox соответственно (или используйте многопользовательскую функцию в Chrome).

  1. После успешного входа в Chrome откройте интерфейс /hello.
  2. После успешного входа в Firefox откройте интерфейс /hello.
  3. Снова откройте интерфейс /hello в Chrome, и вы увидите следующее приглашение:
This session has been expired (possibly due to multiple concurrent logins being attempted as the same user).

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

2.2 Запретить новые входы в систему

Если этот же пользователь уже вошел в систему, вы не хотите его кикать, а хотите запретить новые операции входа, это легко сделать, конфигурация следующая:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .loginPage("/login.html")
            .permitAll()
            .and()
            .csrf().disable()
            .sessionManagement()
            .maximumSessions(1)
            .maxSessionsPreventsLogin(true);
}

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

Разве это не просто?

Но это еще не конец, нам еще нужно предоставить еще один бин:

@Bean
HttpSessionEventPublisher httpSessionEventPublisher() {
    return new HttpSessionEventPublisher();
}

Зачем добавлять этот боб? Потому что в Spring Security он вовремя очищает записи сеанса, прослушивая событие уничтожения сеанса. После того, как пользователь войдет в систему из разных браузеров, будет соответствующий сеанс. Когда пользователь выходит и входит в систему, сеанс будет недействительным, но недействительность по умолчанию достигается путем вызова метода StandardSession#invalidate. Это событие недействительности не может быть воспринимается контейнером Spring.После того, как пользователь выходит из системы и входит в систему, Spring Security не очищает вовремя таблицу информации о сеансе, думая, что пользователь все еще находится в сети, из-за чего пользователь не может снова войти в систему (можно попробовать не добавьте вышеуказанные bean-компоненты, а затем позвольте пользователю выйти из системы после входа в систему. Войдите снова).

Чтобы решить эту проблему, мы предоставляем HttpSessionEventPublisher, который реализует интерфейс HttpSessionListener.В этом bean-компоненте события создания и уничтожения сеанса могут быть обнаружены во времени, а механизм событий в Spring может быть вызван для публикации связанного создания и события уничтожения.Выйти, а затем быть воспринятым Spring Security, исходный код этого класса выглядит следующим образом:

public void sessionCreated(HttpSessionEvent event) {
	HttpSessionCreatedEvent e = new HttpSessionCreatedEvent(event.getSession());
	getContext(event.getSession().getServletContext()).publishEvent(e);
}
public void sessionDestroyed(HttpSessionEvent event) {
	HttpSessionDestroyedEvent e = new HttpSessionDestroyedEvent(event.getSession());
	getContext(event.getSession().getServletContext()).publishEvent(e);
}

ОК, хоть и есть еще одна конфигурация, она все равно очень простая!

3. Принцип реализации

Эта особенность выше, в весенней безопасности - это как это добиться? Давайте проанализируем немного источника.

Прежде всего, мы знаем, что в процессе входа пользователя UsernamePasswordAuthenticationFilter (см.:Song Ge проведет вас через процесс входа в Spring Security), а вызов метода filter в UsernamePasswordAuthenticationFilter запускается в AbstractAuthenticationProcessingFilter, давайте взглянем на вызов метода AbstractAuthenticationProcessingFilter#doFilter:

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
		throws IOException, ServletException {
	HttpServletRequest request = (HttpServletRequest) req;
	HttpServletResponse response = (HttpServletResponse) res;
	if (!requiresAuthentication(request, response)) {
		chain.doFilter(request, response);
		return;
	}
	Authentication authResult;
	try {
		authResult = attemptAuthentication(request, response);
		if (authResult == null) {
			return;
		}
		sessionStrategy.onAuthentication(authResult, request, response);
	}
	catch (InternalAuthenticationServiceException failed) {
		unsuccessfulAuthentication(request, response, failed);
		return;
	}
	catch (AuthenticationException failed) {
		unsuccessfulAuthentication(request, response, failed);
		return;
	}
	// Authentication success
	if (continueChainBeforeSuccessfulAuthentication) {
		chain.doFilter(request, response);
	}
	successfulAuthentication(request, response, chain, authResult);

В этом коде мы видим, что после вызова метода tryAuthentication для завершения процесса аутентификации, после возврата, следующим шагом является вызов метода sessionStrategy.onAuthentication, который используется для решения проблемы параллелизма сеанса. Конкретно:

public class ConcurrentSessionControlAuthenticationStrategy implements
		MessageSourceAware, SessionAuthenticationStrategy {
	public void onAuthentication(Authentication authentication,
			HttpServletRequest request, HttpServletResponse response) {

		final List<SessionInformation> sessions = sessionRegistry.getAllSessions(
				authentication.getPrincipal(), false);

		int sessionCount = sessions.size();
		int allowedSessions = getMaximumSessionsForThisUser(authentication);

		if (sessionCount < allowedSessions) {
			// They haven't got too many login sessions running at present
			return;
		}

		if (allowedSessions == -1) {
			// We permit unlimited logins
			return;
		}

		if (sessionCount == allowedSessions) {
			HttpSession session = request.getSession(false);

			if (session != null) {
				// Only permit it though if this request is associated with one of the
				// already registered sessions
				for (SessionInformation si : sessions) {
					if (si.getSessionId().equals(session.getId())) {
						return;
					}
				}
			}
			// If the session is null, a new one will be created by the parent class,
			// exceeding the allowed number
		}

		allowableSessionsExceeded(sessions, allowedSessions, sessionRegistry);
	}
	protected void allowableSessionsExceeded(List<SessionInformation> sessions,
			int allowableSessions, SessionRegistry registry)
			throws SessionAuthenticationException {
		if (exceptionIfMaximumExceeded || (sessions == null)) {
			throw new SessionAuthenticationException(messages.getMessage(
					"ConcurrentSessionControlAuthenticationStrategy.exceededAllowed",
					new Object[] {allowableSessions},
					"Maximum sessions of {0} for this principal exceeded"));
		}

		// Determine least recently used sessions, and mark them for invalidation
		sessions.sort(Comparator.comparing(SessionInformation::getLastRequest));
		int maximumSessionsExceededBy = sessions.size() - allowableSessions + 1;
		List<SessionInformation> sessionsToBeExpired = sessions.subList(0, maximumSessionsExceededBy);
		for (SessionInformation session: sessionsToBeExpired) {
			session.expireNow();
		}
	}
}

Позвольте мне немного объяснить этот основной код:

  1. Сначала вызовите метод sessionRegistry.getAllSessions, чтобы получить все сеансы текущего пользователя.При вызове этого метода он передает два параметра, один из которых является аутентификацией текущего пользователя, а другой параметр false означает, что сеанс с истекшим сроком действия не включен (после того, как пользователь успешно войдет в систему, это будет Сохранить идентификатор сеанса пользователя, где ключ является принципалом пользователя, а значение представляет собой набор идентификаторов сеансов, соответствующих субъекту).
  2. Затем подсчитайте, сколько действительных сеансов уже имеет текущий пользователь, и получите допустимое количество одновременных сеансов.
  3. Если текущее количество сессий (sessionCount) меньше количества одновременных сессий (allowedSessions), обработка не выполняется, если значение allowSessions равно -1, то это означает, что ограничения на количество сессий нет.
  4. Если текущее количество сессий (sessionCount) равно количеству одновременных сессий (allowedSessions), то сначала проверьте, не является ли текущая сессия нулевой и уже существует ли она в сессиях. ничего не делать. ; Если текущая сессия нулевая, это означает, что будет создана новая сессия, тогда номер текущей сессии (sessionCount) превысит количество параллельных сессий (allowedSessions).
  5. Если предыдущий код не возвращается, он войдет в метод оценки политики allowableSessionsExceeded.
  6. В методе allowSessionsExceeded сначала будет атрибут exceptionIfMaximumExceeded, который является значением maxSessionsPreventsLogin, которое мы настроили в SecurityConfig, значение по умолчанию — false, если оно истинно, исключение будет выбрано напрямую, тогда этот вход завершится ошибкой (соответствует действие раздела 2.2), если оно ложно, сеансы сортируются в соответствии со временем запроса, а затем избыточные сеансы могут быть истечены (в соответствии с действием раздела 2.1).

4. Резюме

Таким образом, две простые строки конфигурации реализуют управление параллелизмом сеансов в весенней безопасности. Разве это не просто? Тем не менее, здесь еще есть небольшая яма, песня GE будет продолжать анализировать с вами в следующей статье.

Этот кейс можно скачать с Github:GitHub.com/Len VE/Судный день, ты…

Ну, я не знаю, дошли ли до этого друзья? Если появится GET, не забудьте нажать, посмотреть и подбодрить Songge~