После успешного входа предыдущий вошедший в систему пользователь будет автоматически удален.Сун Гэ впервые увидел эту функцию в Koukou.В то время он подумал, что это было очень интересно.
После того, как я разработал его сам, я также столкнулся с точно такими же потребностями.Поскольку недавняя серия Spring Security сериализуется, я обсужу с вами, как реализовать эту функцию в сочетании со Spring Security.
Эта статья - тринадцатая статья в этой серии. Чтение предыдущих статей поможет вам лучше понять эту статью:
- Выкопайте большую яму и позвольте Spring Security сделать это!
- Song Ge поможет вам начать работу с Spring Security, не спрашивайте, как снова расшифровать пароль.
- Научите, как настроить вход в форму в Spring Security
- Spring Security разделяет внешний и внутренний интерфейсы, давайте не будем переходить по страницам! Все взаимодействия JSON
- Операции авторизации в Spring Security оказались такими простыми
- Как Spring Security хранит пользовательские данные в базе данных?
- Spring Security+Spring Data Jpa объединяют усилия, управление безопасностью становится еще проще!
- Spring Boot + Spring Security реализует функцию автоматического входа в систему
- Spring Boot автоматически входит в систему, как контролировать риски безопасности?
- В проекте микросервиса, где Spring Security лучше, чем Shiro?
- Два способа для Spring Security настроить логику аутентификации (расширенный игровой процесс)
- Как быстро просмотреть 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).
- После успешного входа в Chrome откройте интерфейс /hello.
- После успешного входа в Firefox откройте интерфейс /hello.
- Снова откройте интерфейс /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();
}
}
}
Позвольте мне немного объяснить этот основной код:
- Сначала вызовите метод sessionRegistry.getAllSessions, чтобы получить все сеансы текущего пользователя.При вызове этого метода он передает два параметра, один из которых является аутентификацией текущего пользователя, а другой параметр false означает, что сеанс с истекшим сроком действия не включен (после того, как пользователь успешно войдет в систему, это будет Сохранить идентификатор сеанса пользователя, где ключ является принципалом пользователя, а значение представляет собой набор идентификаторов сеансов, соответствующих субъекту).
- Затем подсчитайте, сколько действительных сеансов уже имеет текущий пользователь, и получите допустимое количество одновременных сеансов.
- Если текущее количество сессий (sessionCount) меньше количества одновременных сессий (allowedSessions), обработка не выполняется, если значение allowSessions равно -1, то это означает, что ограничения на количество сессий нет.
- Если текущее количество сессий (sessionCount) равно количеству одновременных сессий (allowedSessions), то сначала проверьте, не является ли текущая сессия нулевой и уже существует ли она в сессиях. ничего не делать. ; Если текущая сессия нулевая, это означает, что будет создана новая сессия, тогда номер текущей сессии (sessionCount) превысит количество параллельных сессий (allowedSessions).
- Если предыдущий код не возвращается, он войдет в метод оценки политики allowableSessionsExceeded.
- В методе allowSessionsExceeded сначала будет атрибут exceptionIfMaximumExceeded, который является значением maxSessionsPreventsLogin, которое мы настроили в SecurityConfig, значение по умолчанию — false, если оно истинно, исключение будет выбрано напрямую, тогда этот вход завершится ошибкой (соответствует действие раздела 2.2), если оно ложно, сеансы сортируются в соответствии со временем запроса, а затем избыточные сеансы могут быть истечены (в соответствии с действием раздела 2.1).
4. Резюме
Таким образом, две простые строки конфигурации реализуют управление параллелизмом сеансов в весенней безопасности. Разве это не просто? Тем не менее, здесь еще есть небольшая яма, песня GE будет продолжать анализировать с вами в следующей статье.
Этот кейс можно скачать с Github:GitHub.com/Len VE/Судный день, ты…
Ну, я не знаю, дошли ли до этого друзья? Если появится GET, не забудьте нажать, посмотреть и подбодрить Songge~