Spring Security 6.x подробно объясняет механизм управления аутентификацией сеанса в этой статье.
Spring Security 6.x подробно объясняет механизм управления аутентификацией сеанса в этой статье.

Предыдущие статьи в основном были посвящены содержанию, связанному с аутентификацией личности. Сегодня мы в основном обсуждаем поддержание статуса аутентификации. Поскольку протокол HTTP не имеет состояния, после успешной аутентификации, чтобы позволить последующим запросам продолжать поддерживать статус аутентификации. Чтобы избежать необходимости повторно инициировать процесс аутентификации для каждого запроса, вам необходимо сохранить результаты аутентификации, а затем запросить и восстановить соответствующую аутентификацию при поступлении нового запроса. Обычно существует две схемы реализации. Одна — классическая схема сеанса cookie, которая получает доступ к информации аутентификации в атрибуте сеанса сервера. Преимущество заключается в том, что метод реализации относительно прост. Другой — схема токена. использует некоторые алгоритмы. Преимущество кодирования и декодирования аутентификационной информации заключается в том, что ее не нужно реализовывать, что эффективно снижает нагрузку на хранилище на стороне сервера. В этой статье в основном рассматривается Spring. Аутентификация на основе сеанса и общие механизмы управления в структуре безопасности.

1. Базовая реализация Session в Tomcat

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

Примечание. Показанный ниже сеанс представляет собой интерфейс, определенный в Tomcat.,И то, что мы обычно называем сеансом,это интерфейс HttpSession, определенный в jakarta.servlet.http (или java.servlet.http),В Tomcat у них есть общая реализация, добрая, как StandardSession.,через фасадный режим,В конце концов, фактическими операциями являются объекты StandardSession.

В Tomcat ManagerBase в основном отвечает за поддержание объекта сеанса. Исходный код выглядит следующим образом. Вы можете видеть, что объект сеанса фактически хранится в ConcurrentHashMap, а его ключ — sessionId в соответствии со значением JSESSIONID в файле cookie. по запросу вы можете запросить его через метод findSession к соответствующему объекту сеанса.

Язык кода:java
копировать
public abstract class ManagerBase extends LifecycleMBeanBase implements Manager {
    ...
    protected Map<String,Session> sessions = new ConcurrentHashMap<>();
    ...
    
    @Override
    public Session findSession(String id) throws IOException {
        if (id == null) {
            return null;
        }
        return sessions.get(id);
    }
    
        @Override
    public void add(Session session) {
        sessions.put(session.getIdInternal(), session);
        int size = getActiveSessions();
        if (size > maxActive) {
            synchronized (maxActiveUpdateLock) {
                if (size > maxActive) {
                    maxActive = size;
                }
            }
        }
    }
    
    @Override
    public Session createSession(String sessionId) {
    
        if ((maxActiveSessions >= 0) && (getActiveSessions() >= maxActiveSessions)) {
            rejectedSessions++;
            throw new TooManyActiveSessionsException(sm.getString("managerBase.createSession.ise"), maxActiveSessions);
        }
    
        // Recycle or create a Session instance
        Session session = createEmptySession();
    
        // Initialize the properties of the new session and return it
        session.setNew(true);
        session.setValid(true);
        session.setCreationTime(System.currentTimeMillis());
        session.setMaxInactiveInterval(getContext().getSessionTimeout() * 60);
        String id = sessionId;
        if (id == null) {
            id = generateSessionId();
        }
        session.setId(id); // Внутри этого метода будет вызван метод добавления, указанный выше, для сохранения сеанса в ConcurrentHashMap.
    
        SessionTiming timing = new SessionTiming(session.getCreationTime(), 0);
        synchronized (sessionCreationTiming) {
            sessionCreationTiming.add(timing);
            sessionCreationTiming.poll();
        }
        return session;
    }
}

кроме того,Метод createSession может генерировать новый объект сеанса.,Будет вызван метод session#setId,В это время сеанс будет сохранен в ConcurrentHashMap.,Этот метод в основном используется в сценариях, где HttpServleRequest получает текущий сеанс.,Вы можете еще раз взглянуть на реализацию HttpServleRequest в Tomcat добрыйRequest.,При вызове метода Request#doGetSession,Если объект сеанса в данный момент не найден,Будет вызван метод createSession,Создать новый сеанс,Затем создайте соответствующий файл cookie,Его имя по умолчанию — «JSESSIONID».,и добавлен в ответ,Наконец, возвращается объект сеанса.

Язык кода:java
копировать
@Override
public HttpSession getSession(boolean create) {
    Session session = doGetSession(create);
    if (session == null) {
        return null;
    }

    return session.getSession(); // Фасадный режим: инкапсулируйте сам сеанс в объект StandardSessionFacade и возвращайте его. StandardSessionFacade — это реализация интерфейса HttpSession в Tomcatдобрый.
}

protected Session doGetSession(boolean create) {
    Context context = getContext();
    ...
    Manager manager = context.getManager();
    ...
    if (requestedSessionId != null) { // Из файлов cookie SessionId, проанализированный из JSESSIONID, также может быть нулевым.
        try {
            session = manager.findSession(requestedSessionId); // Поиск ConcurrentHashMap в менеджере
        } catch (IOException e) {
           ...
        }
        ...
        if (session != null) {
            session.access(); // Фиксируйте количество посещений сеанса
            return session;
        }
    }

    // Create a new session if requested and the response is not committed
    if (!create) {
        return null;
    }
    ...
    String sessionId = getRequestedSessionId(); 
    ...
    session = manager.createSession(sessionId); // Создать новый сеанс
    // Creating a new session cookie based on that session
    if (session != null && trackModesIncludesCookie) {
        Cookie cookie =
                ApplicationSessionCookieConfig.createSessionCookie(context, session.getIdInternal(), isSecure()); // Здесь будет создан файл cookie с именем «JSESSIONID».
        response.addSessionCookieInternal(cookie);
    }

    if (session == null) {
        return null;
    }

    session.access();
    return session;
}

Выше приведена базовая реализация создания и чтения сеанса в tomcat. Из-за ограничений по объему другие методы, связанные с сеансом, здесь не представлены.

2. Основной процесс доступа к SecurityContext

Во-первых, давайте кратко представим, как сохранять и читать объект SecurityContext с помощью механизма Session. Фактически, весь процесс в основном инкапсулируется инфраструктурой Spring Security, которая не требует слишком больших затрат на разработку для разработчиков. Весь процесс примерно представлен простой диаграммой последовательности:

Подводя итог, можно выделить в основном следующие этапы:

  1. Обычно в конкретной реализации каждого механизма аутентификации (представленного на рисунке AbstractAuthenticationProcessingFilter) каждый раз при успешной аутентификации создается объект SecurityContext и устанавливается аутентифицированный объект Authentication.
  2. проходитьSecurityContextRepositoryВоляSecurityContextСохранено в атрибутах сеанса.,SecurityContextRepository имеет несколько реализаций интерфейса.,Дальнейшее введение ниже
  3. Запишите идентификатор сеанса в Cookie.
  4. Последующие запросы будут переносить этот файл cookie при повторном доступе. В SecurityContextHolderFilter SecurityContextRepository также используется для получения соответствующего объекта сеанса, а затем соответствующий объект SecurityContext извлекается из атрибутов сеанса и устанавливается в ThreadLocal SecurityContextHolder для других нисходящих потоков. компоненты.

Давайте посмотрим на конкретные детали реализации.

2.1 Сохранить контекст безопасности

Упоминалось выше для Сохранить контекст интерфейсаSecurityContextRepository безопасности, его добрая реализация по умолчанию — DelegatingSecurityContextRepository, а его метод сохранения делегируется двум объектам в конфигурации по умолчанию, а именно HttpSessionS. ecurityContextRepository и RequestAttributeSecurityContextRepository, где RequestAttributeSecurityContextRepository фактически не сохраняется, а просто сохраняет SecurityCo. ntext сохраняется в атрибуте запроса, поэтому в других последующих запросах объект SecurityContext не может быть получен. Он применим только к сценарию внутренней отправки, а HttpSessionSecurityContextRepository в основном отвечает за использование сеанса для достижения постоянства. относительно Относительно просто: сначала сгенерируйте сеанс методом request#getSession, затем запишите объект SecurityContext в атрибут сеанса (SPRING_SECURITY_CONTEXT) и, наконец, установите соответствующий Cookie в ответе и запишите его в клиент браузера. Ниже приведен исходный код метода HttpSessionSecurityContextRepository#saveContext, Весной. В новой версии Security, как правило, маловероятно, что объект SaveContextOnUpdateOrErrorResponseWrapper не является пустым (причина будет объяснена в разделе 2.3. Фактический метод сохранения объекта SecurityContext — setContextInSession).

Язык кода:java
копировать
@Override
public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) {
    SaveContextOnUpdateOrErrorResponseWrapper responseWrapper = WebUtils.getNativeResponse(response,
          SaveContextOnUpdateOrErrorResponseWrapper.class);
    if (responseWrapper == null) {
       saveContextInHttpSession(context, request); // Обычно этот метод выполняется
       return;
    }
    responseWrapper.saveContext(context);
}

private void saveContextInHttpSession(SecurityContext context, HttpServletRequest request) {
    if (isTransient(context) || isTransient(context.getAuthentication())) {
       return;
    }
    SecurityContext emptyContext = generateNewContext();
    if (emptyContext.equals(context)) {
       HttpSession session = request.getSession(false);
       removeContextFromSession(context, session);
    }
    else {
       boolean createSession = this.allowSessionCreation;
       HttpSession session = request.getSession(createSession);
       setContextInSession(context, session);
    }
}

private void setContextInSession(SecurityContext context, HttpSession session) {
    if (session != null) {
       session.setAttribute(this.springSecurityContextKey, context); //Сохраняем объект SecurityContext в атрибуте сеанса, где значением по умолчанию для SpringSecurityContextKey является «SPRING_SECURITY_CONTEXT_KEY»
       if (this.logger.isDebugEnabled()) {
          this.logger.debug(LogMessage.format("Stored %s to HttpSession [%s]", context, session));
       }
    }
}

2.2 Загрузка контекста безопасности

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

Язык кода:java
копировать
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
       throws ServletException, IOException {
    if (request.getAttribute(FILTER_APPLIED) != null) {
       chain.doFilter(request, response);
       return;
    }
    request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
    Supplier<SecurityContext> deferredContext = this.securityContextRepository.loadDeferredContext(request);
    try {
       this.securityContextHolderStrategy.setDeferredContext(deferredContext);
       chain.doFilter(request, response);
    }
    finally {
       this.securityContextHolderStrategy.clearContext();
       request.removeAttribute(FILTER_APPLIED);
    }
}

Метод loadDeferredContext объекта SecurityContextRepository вызывается при загрузке. Этот метод возвращает объект отложенного доступа в режиме поставщика (просто понимаемый как возврат записи для доступа к объекту SecurityContext, которому требуется только доступ к Secur). ityContext, что может повысить определенную эффективность). Если процесс аутентификации не был инициирован ранее, здесь будет создан пустой SecurityContext. Если он был аутентифицирован, предыдущий процесс аутентификации будет получен из сохраненного экземпляра SecurityContext. . Ниже приведен исходный код метода HttpSessionSecurityContextRepository#loadDeferredContext.

Язык кода:java
копировать
public DeferredSecurityContext loadDeferredContext(HttpServletRequest request) {
    Supplier<SecurityContext> supplier = () -> readSecurityContextFromSession(request.getSession(false));
    return new SupplierDeferredSecurityContext(supplier, this.securityContextHolderStrategy);
}

private SecurityContext readSecurityContextFromSession(HttpSession httpSession) {
    if (httpSession == null) {
       this.logger.trace("No HttpSession currently exists");
       return null;
    }
    // Session exists, so try to obtain a context from it.
    Object contextFromSession = httpSession.getAttribute(this.springSecurityContextKey); //Получаем значение атрибута, соответствующее сессии, через SpringSecurityContextKey и получаем объект SecurityContext через доброе преобразование типа
   ...
    // Everything OK. The only non-null return from this method.
    return (SecurityContext) contextFromSession;
}

2.3 Изменения в новой версии

Фактически, до Spring Security 5.7 загрузка SecurityContext обрабатывалась не SecurityContextHolderFilter, а SecurityContextPersistenceFilter. Между ними есть большая разница. SecurityContextPersistenceFilter также отвечает за автоматическое сохранение объекта SecurityContext. Наконец, взгляните на его метод doFilter. блок кода, здесь метод saveContext SecurityContextRepository будет вызываться один раз.

Язык кода:java
копировать
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
       throws IOException, ServletException {
    ...
    HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
    SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
    try {
       this.securityContextHolderStrategy.setContext(contextBeforeChainExecution);
      ...
       chain.doFilter(holder.getRequest(), holder.getResponse());
    }
    finally {
       SecurityContext contextAfterChainExecution = this.securityContextHolderStrategy.getContext();
       // Crucial removal of SecurityContextHolder contents before anything else.
       this.securityContextHolderStrategy.clearContext();
       this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse()); // Больше не требуется автоматическое сохранение в SecurityContextHolderFilter. контекст безопасности
       request.removeAttribute(FILTER_APPLIED);
       this.logger.debug("Cleared SecurityContextHolder to complete request");
    }
}

В старой версии каждый механизм аутентификации не использует напрямую SecurityContextRepository для сохранения объекта SecurityContext, созданного после аутентификации. Поэтому перед отправкой ответа, если SecurityContext изменяется после выполнения этого запроса, например, создается новый аутентифицированный объект аутентификации. set, то SecurityContext необходимо сохранить.

Например, фильтр RememberMeAuthenticationFilter используется для реализации механизма входа в систему «запомнить меня», то есть для определения статуса входа с помощью определенного файла cookie, что позволяет избежать повторной инициации запросов аутентификации в течение относительно длительного периода времени. Исходный код. Spring Security 5.6 выглядит следующим образом

Язык кода:java
копировать
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
       throws IOException, ServletException {
    ...
    Authentication rememberMeAuth = this.rememberMeServices.autoLogin(request, response);
    if (rememberMeAuth != null) {
       // Attempt authentication via AuthenticationManager
       try {
          rememberMeAuth = this.authenticationManager.authenticate(rememberMeAuth);
          // Store to SecurityContextHolder
      SecurityContext context = SecurityContextHolder.createEmptyContext();
          context.setAuthentication(rememberMeAuth);
      SecurityContextHolder.setContext(context);
          onSuccessfulAuthentication(request, response, rememberMeAuth);
          // this.securityContextRepository.saveContext(context, request, response); Весной Security 5.7 и выше, добавлены сохранения контекст безопасностишаги          ...
       }
       catch (AuthenticationException ex) {
          ...
       }
    }
    chain.doFilter(request, response);
}

можно увидеть,После прохождения аутентификации RememberMe AuthenticationProvider,Создал новый SecurityContext.,Объект аутентификации установлен,Затем размещается SecurityContextHolder.,Но в конце концов это длилось недолго.,Итак, прежде чем будет отправлен окончательный ответ,Сохранение должно быть завершено единообразно с помощью SecurityContextPersistenceFilter.,Этот механизм кажется разумным,Позволяет избежать необходимости использовать SecurityContextRepository для каждого механизма аутентификации.,Так почему же в новой версии отсутствует фильтр SecurityContextPersistenceFilter? в официальной документации,Этому есть определенное объяснение: потому что происходят изменения в SecurityContext,Этот процесс отслеживания относительно сложен.,Вы можете проверить исходный код старой версии,Он использует различные оболочки HttpServletResponse добрый.,В различных методах Repsonse,Например, перенаправление sendRedirect,Исключения, такие как sendError, были скрыты.,Суждение, основанное на многих условиях,Чтобы определить, следует ли сохранять контекст безопасности,Это вызовет несколько операций чтения и записи HttpSession.,Но на самом деле большинство операций чтения и записи не являются необходимыми.,После выполнения каждого запроса,Вам предстоит пройти эти операции,По соображениям эффективности и производительности,В новой версии убрана логика автоматического сохранения.,Делает процесс доступа SecurityContext более легким.

3. Механизм управления сеансами

3.1 Основные компоненты

интерфейс

  • SessionAuthenticationStrategy: определен только один метод.,то есть onAuthentication,То есть применить разные стратегии управления сеансом к текущей аутентификации.,Имеет несколько распространенных реализаций добрыйConcurrentSessionControlAuthenticationStrategy.,ChangeSessionIdAuthenticationStrategy и т. д. (подробно ниже).
  • sessionRegistry: в основном определяет чтение,Новый,Методы сохранения SessionInfomation, такие как удаление.,Обычно используется в сценариях управления параллелизмом сеансов.,Реализация доброго по умолчанию — SessionRegistryImpl.,Две карты поддерживаются внутри компании,А именно участники и идентификаторы сеансов,Первый поддерживает соответствующую связь между субъектом и идентификатором сеанса (один ко многим).,Последний поддерживает связь между объектами sessionId и SessionInformation (один к одному).

добрый

  • SessionInformation: его функция эквивалентна объекту метки, используемому для соответствия HttpSession в среде Spring Security. Он имеет следующие переменные-члены.
Язык кода:java
копировать
public class SessionInformation implements Serializable {
    ...
    private Date lastRequest; // Самое последнее время доступа. При каждом запросе, если текущий сеанс не истек, время будет обновляться.
    private final Object principal; // Идентифицирует текущего пользователя, обычно имя пользователя
    private final String sessionId; // SessionId, соответствующий HttpSession.
    private boolean expired = false; // Определяет, истек ли текущий сеанс
    ...    
}

Фильтры, связанные с управлением сеансами

  • SessionManagementFilter: этот фильтр наиболее отвечает за управление сеансами.,Как упоминалось в предыдущем разделе, фильтр SecurityContextHolderFilter отвечает за загрузку SecurityContext в SecurityContextHolderStrategy.,Здесь SessionManagementFilter используется для определения того, прошел ли объект аутентификации в SecurityContext аутентификацию.,если аутентифицирован,Он вызовет стратегии, соответствующие различным реализациям SessionAuthenticationStrategy, для обработки текущего сеанса.,Например, управление параллелизмом сеансов,Атаки фиксации сеанса и т. д.,Однако в новой версии SessionManagementFilter по умолчанию не включен.,Объяснение, данное в официальной документации, заключается в том, что каждый запрос должен прочитать сеанс, чтобы получить объект SecurityContext.,Это более или менее приведет к некоторой потере производительности.,Итак, теперь эту работу выполняет сам механизм аутентификации.,То есть,Стратегия управления сеансом будет применяться только после прохождения проверки подлинности.,и только один звонок,Это позволяет избежать проблемы чтения сеанса для каждого запроса.,Также принадлежит Весне Одно из легких улучшений платформы безопасности.
  • ConcurrentSessionFilter: этот фильтр в основном имеет две функции: одна — очистить текущую информацию о сеансе, если срок ее действия истек. Другая — обновить время обновления последней информации о сеансе и выполнить логику выхода из системы. Он используется для сценариев управления параллелизмом сеанса. Подробности ниже)
  • DisableEncodeUrlFilter: этот фильтр был представлен в версии 5.7. Он в основном используется для запрета перекодирования URL-адресов. Когда клиентские файлы cookie отключены, ответ по умолчанию включает идентификатор сеанса в URL-адрес, что раскрывает сеанс и обеспечивает определенную скрытую опасность. поэтому этот фильтр включен по умолчанию.
  • ForceEagerSessionCreationFilter: этот фильтр был представлен в версии 5.7. Если sessionCreationPolicy настроен как SessionCreationPolicy.ALWAYS, он будет добавлен в цепочку фильтров и имеет очень высокий приоритет. Его функция заключается в том, что запрос попадает в цепочку фильтров в самом начале. создать объект сеанса. Хотя это не более экономичный способ, это очень необходимо, если вы хотите использовать сеанс для отслеживания некоторой информации о клиенте.

Вот некоторые распространенные сценарии управления сеансами:

3.2 Управление параллельным сеансом

Управление параллелизмом сеансов. Наиболее распространенным сценарием является ограничение одной учетной записи, позволяющей нескольким клиентам входить в систему одновременно. То есть, когда второй клиент инициирует вход в систему и проходит аутентификацию, срок действия ранее проверенного сеанса будет установлен. и пользователь больше не сможет одновременно войти в систему. Вам придется выйти из системы.

Конфигурация также очень проста: элемент конфигурации sessionConcurrency в sessionMangement DSL настраивает maxinumSession на «1».

Язык кода:java
копировать
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    ...
    http.sessionManagement(session -> session.sessionConcurrency(concurrency -> concurrency.maximumSessions(1)));
    ...
    return http.build();
}

Конкретную реализацию завершает реализация стратегии добрыйConcurrentSessionControlAuthenticationStrategy и фильтр ConcurrentSessionFilter.,Первый в ConcurrentSessionControlAuthenticationStrategy,Установите срок действия определенных сеансов, которые не соответствуют требованиям, на основании правил.,Продолжайте юридические заседания,Таким образом, очищая его в ConcurrentSessionFilter,Или обновите время обновления RefLastRequest.

Давайте сначала посмотрим на исходный код метода ConcurrentSessionControlAuthenticationStrategy#onAuthentication, где разрешенные сеансы — это максимальное количество сеансов, которое мы установили в конфигурации, а затем получим все сеансы текущего пользователя через sessionRegistry. ionInformation и подсчитать его количество. При превышении указанного максимального количества сеансов вызывается методallowableSessionsExceeded, который будет отсортирован по времени LastRequest. Наконец, для тех сеансов, которые превышают максимальное количество сеансов и имеют более раннее время, будет установлено время истечения.

Язык кода:java
копировать
public void onAuthentication(Authentication authentication, HttpServletRequest request,
       HttpServletResponse response) {
    int allowedSessions = getMaximumSessionsForThisUser(authentication);
    if (allowedSessions == -1) { // Если параметр MaximumSessions равен -1, это означает, что количество сеансов не ограничено.
       // We permit unlimited logins
       return;
    }
    List<SessionInformation> sessions = this.sessionRegistry.getAllSessions(authentication.getPrincipal(), false);
    int sessionCount = sessions.size();
    if (sessionCount < allowedSessions) {
       // They haven't got too many login sessions running at present
       return;
    }
    if (sessionCount == allowedSessions) { // Если количество sessionInformations, принадлежащих пользователю, точно равно MaximumSessions, определите, включен ли текущий идентификатор сеанса в эти sessionInformations. Если нет, это означает, что сеанс необходимо признать недействительным.
       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, this.sessionRegistry);
}

protected void allowableSessionsExceeded(List<SessionInformation> sessions, int allowableSessions,
       SessionRegistry registry) throws SessionAuthenticationException {
    if (this.exceptionIfMaximumExceeded || (sessions == null)) {
       throw new SessionAuthenticationException(
             this.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();
    }
}

И в ConcurrentSessionFilter,Будет запрошен объект SessionInformation, соответствующий текущему идентификатору сеанса.,Определите, помечен ли он как просроченный,Если срок его действия не истек,Затем вызовите sessionRegistry#refreshLastRequest, чтобы обновить время.,Если срок его действия истек,затем вызовите логику выхода из системы,Включая признание недействительным объекта HttpSession.,Очистите объекты SecurityContext в SecurityContextRepository и т. д.,Поэтому его приоритет в SecurityFilterChain обычно стоит после SessionManagementFilter (ранняя версия) и Filter, соответствующих различным механизмам аутентификации (таким как реализация AbstractAuthenticationProcessingFilter добрый),Ниже приведен исходный код метода doFilter.

Язык кода:java
копировать
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
       throws IOException, ServletException {
    HttpSession session = request.getSession(false);
    if (session != null) {
       SessionInformation info = this.sessionRegistry.getSessionInformation(session.getId());
       if (info != null) {
          if (info.isExpired()) {
             // Expired - abort processing
             this.logger.debug(LogMessage
                .of(() -> "Requested session ID " + request.getRequestedSessionId() + " has expired."));
             doLogout(request, response); // sessionInformation помечается как истекший, выполняется логика выхода из системы и реализуется политика истечения срока действия.
             this.sessionInformationExpiredStrategy
                .onExpiredSessionDetected(new SessionInformationExpiredEvent(info, request, response)); //Реализацией по умолчанию является ResponseBodySessionInformationExpiredStrategy, которая записывает в Response текст, информирующий пользователя о том, что срок действия сеанса истек.
             return;
          }
          // Non-expired - update last request date/time
          this.sessionRegistry.refreshLastRequest(info.getSessionId()); // Для сеансов с истекшим сроком действия обновите время последнего запроса.
       }
    }
    chain.doFilter(request, response);
}

3.3 Регистрация сеанса

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

Язык кода:java
копировать
public class RegisterSessionAuthenticationStrategy implements SessionAuthenticationStrategy {

    private final SessionRegistry sessionRegistry;

    ...
    @Override
    public void onAuthentication(Authentication authentication, HttpServletRequest request,
          HttpServletResponse response) {
       this.sessionRegistry.registerNewSession(request.getSession().getId(), authentication.getPrincipal());
    }

}

public class SessionRegistryImpl implements SessionRegistry, ApplicationListener<AbstractSessionEvent> {
    ...
    private final ConcurrentMap<Object, Set<String>> principals;

    private final Map<String, SessionInformation> sessionIds;
    ...
    @Override
    public void registerNewSession(String sessionId, Object principal) {
       ...
        if (getSessionInformation(sessionId) != null) {
           removeSessionInformation(sessionId);
        }
       ...
        this.sessionIds.put(sessionId, new SessionInformation(principal, sessionId, new Date()));
        this.principals.compute(principal, (key, sessionsUsedByPrincipal) -> {
           if (sessionsUsedByPrincipal == null) {
              sessionsUsedByPrincipal = new CopyOnWriteArraySet<>();
           }
           sessionsUsedByPrincipal.add(sessionId);
           this.logger.trace(LogMessage.format("Sessions used by '%s' : %s", principal, sessionsUsedByPrincipal));
           return sessionsUsedByPrincipal;
        });
    }
}

Если во время регистрации исходный sessionId имеет соответствующий объект SessionInformation, сначала очистите его, затем создайте новый объект SessionInformation, запишите связь между sessionId и объектом SessionInformation в карту sessionIds и запишите связь между субъектом и sessionId в карту. руководителей.

3.4 Защита от фиксированных атак сеанса

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

Весной Безопасность предлагает 3 настраиваемые защиты. от фиксированных атак стратегии сеанса, а именно:changeSessionId, newSession иmigrSession (настроенные следующим образом), гдеchangeSessionId соответствует реализации ChangeSessionIdAuthenticationStrategy добрый, newSession иmigrSession, соответствующий реализации SessionFixationProtectionStrategy. Первый не требует создания нового сеанса, а последние два создают новый сеанс. Разница в том, что newSession не сохраняет значения атрибутов исходного сеанса. (только весна собственные атрибуты безопасности), а миграция Session перенесет исходные атрибуты сеанса.

Язык кода:java
копировать
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    ...
    http.sessionManagement(session -> session.sessionFixation(fixation -> fixation.changeSessionId()));
    http.sessionManagement(session -> session.sessionFixation(fixation -> fixation.newSession()));
    http.sessionManagement(session -> session.sessionFixation(fixation -> fixation.migrateSession()));
    ...
    return http.build();
}

Конкретные детали двух реализаций доброго представлены ниже.

Реализация ChangeSessionIdAuthenticationStrategy является самой простой. Исходный код следующий: он использует метод HttpServletRequest#changeSessionId для присвоения нового sessionId текущему сеансу. ConcurrentHashMap, поддерживаемый ManagerBase, обновляется, старый sessionId удаляется, а новый sessionId добавляется в качестве ключа. Таким образом, старый sessionId не может использоваться для запроса объекта сеанса. Этот метод реализации является относительно легким, но он также является. полагается на поддержку базового контейнера, поэтому его можно использовать только в Servlet 3.1и более новые версии контейнера.,Это также реализация по умолчаниюдобрый。

Язык кода:java
копировать
public final class ChangeSessionIdAuthenticationStrategy extends AbstractSessionFixationProtectionStrategy {

    @Override
    HttpSession applySessionFixation(HttpServletRequest request) {
       request.changeSessionId();
       return request.getSession();
    }

}

Реализация SessionFixationProtectionStrategy немного сложнее. Она эквивалентна работе по миграции сеанса: сначала атрибуты исходного сеанса временно сохраняются в Map (упоминалось выше, при выборе newSession только Spring). самоопределенные атрибуты безопасности), а затем аннулируют старый сеанс и создают новый сеанс. Наконец, все атрибуты, которые были временно сохранены в карте, переносятся. Этот метод реализации относительно тяжелый, поэтому он используется только в Servlet3. 0 и более ранние версии контейнера в качестве реализации по умолчанию.

Язык кода:java
копировать
final HttpSession applySessionFixation(HttpServletRequest request) {
    HttpSession session = request.getSession();
    String originalSessionId = session.getId();
    this.logger.debug(LogMessage.of(() -> "Invalidating session with Id '" + originalSessionId + "' "
          + (this.migrateSessionAttributes ? "and" : "without") + " migrating attributes."));
    Map<String, Object> attributesToMigrate = extractAttributes(session);
    int maxInactiveIntervalToMigrate = session.getMaxInactiveInterval();
    session.invalidate();
    session = request.getSession(true); // we now have a new session
    this.logger.debug(LogMessage.format("Started new session: %s", session.getId()));
    transferAttributes(attributesToMigrate, session);
    if (this.migrateSessionAttributes) {
       session.setMaxInactiveInterval(maxInactiveIntervalToMigrate);
    }
    return session;
}

4. Резюме

В этой статье представлена ​​базовая реализация сеанса, процесс доступа к SecurityContext в сеансе и общие сценарии управления сеансом. Наконец, подводится итог:

  • Сеанс — это объект, хранящийся на сервере. При создании объекта сеанса к ответу будет добавлен файл cookie. Значением файла cookie является идентификатор сеанса.
  • В Tomcat ManagerBase отвечает за поддержку объекта сеанса. Переменная сеанса ConcurrentHashMap определяется внутри, а ее ключ — sessionId, который используется для хранения и запроса объектов сеанса.
  • В новой версии Spring Security из соображений производительности SecurityContextPersistenceFilter больше не действует по умолчанию и заменяется SecurityContextHolderFilter, который в основном отвечает за загрузку аутентифицированных объектов SecurityContext в SecurityContextHolderStrategy через SecurityContextRepository.
  • За работу по хранению SecurityContext отвечает каждая реализация механизма аутентификации.,Конкретная логика хранения выполнения находится в HttpSessionSecurityContextRepository.,Сохранить контекст объект безопасности, то есть значение атрибута «SPRING_SECURITY_CONTEXT», записанное в сеанс.
  • Платформа Spring Security предоставляет несколько конфигураций управления сеансами. Общие из них включают управление параллельным сеансом и фиксированные атаки сеанса.
  • Управление параллелизмом сессий в основном завершается реализацией стратегии добрыйConcurrentSessionControlAuthenticationStrategy и фильтром ConcurrentSessionFilter.,Первый отвечает за пометку сессий, не соответствующих правилам, как истекших.,Последний отвечает за очистку этих просроченных сеансов.
  • Существует три соответствующие конфигурации для защиты от атак с фиксированным сеансом:changeSessionId, newSession иmigrateSession. ChangeSessionId в основном реализуется ChangeSessionIdAuthenticationStrategy, а последние две реализуются SessionFixationProtectionStra. tegy, реализация ChangeSessionIdAuthenticationStrategy относительно легкая и простая, то есть создание нового sessionId и присвоение его текущему сеансу. Реализация SessionFixationProtectionStrategy относительно сложна и требует создания нового сеанса, а затем переноса атрибутов исходного сеанса.

Я участвую в последнем конкурсе эссе для специального учебного лагеря Tencent Technology Creation 2024. Приходите и разделите со мной приз!

boy illustration
Неразрушающее увеличение изображений одним щелчком мыши, чтобы сделать их более четкими артефактами искусственного интеллекта, включая руководства по установке и использованию.
boy illustration
Копикодер: этот инструмент отлично работает с Cursor, Bolt и V0! Предоставьте более качественные подсказки для разработки интерфейса (создание навигационного веб-сайта с использованием искусственного интеллекта).
boy illustration
Новый бесплатный RooCline превосходит Cline v3.1? ! Быстрее, умнее и лучше вилка Cline! (Независимое программирование AI, порог 0)
boy illustration
Разработав более 10 проектов с помощью Cursor, я собрал 10 примеров и 60 подсказок.
boy illustration
Я потратил 72 часа на изучение курсорных агентов, и вот неоспоримые факты, которыми я должен поделиться!
boy illustration
Идеальная интеграция Cursor и DeepSeek API
boy illustration
DeepSeek V3 снижает затраты на обучение больших моделей
boy illustration
Артефакт, увеличивающий количество очков: на основе улучшения характеристик препятствия малым целям Yolov8 (SEAM, MultiSEAM).
boy illustration
DeepSeek V3 раскручивался уже три дня. Сегодня я попробовал самопровозглашенную модель «ChatGPT».
boy illustration
Open Devin — инженер-программист искусственного интеллекта с открытым исходным кодом, который меньше программирует и больше создает.
boy illustration
Эксклюзивное оригинальное улучшение YOLOv8: собственная разработка SPPF | SPPF сочетается с воспринимаемой большой сверткой ядра UniRepLK, а свертка с большим ядром + без расширения улучшает восприимчивое поле
boy illustration
Популярное и подробное объяснение DeepSeek-V3: от его появления до преимуществ и сравнения с GPT-4o.
boy illustration
9 основных словесных инструкций по доработке академических работ с помощью ChatGPT, эффективных и практичных, которые стоит собрать
boy illustration
Вызовите deepseek в vscode для реализации программирования с помощью искусственного интеллекта.
boy illustration
Познакомьтесь с принципами сверточных нейронных сетей (CNN) в одной статье (суперподробно)
boy illustration
50,3 тыс. звезд! Immich: автономное решение для резервного копирования фотографий и видео, которое экономит деньги и избавляет от беспокойства.
boy illustration
Cloud Native|Практика: установка Dashbaord для K8s, графика неплохая
boy illustration
Краткий обзор статьи — использование синтетических данных при обучении больших моделей и оптимизации производительности
boy illustration
MiniPerplx: новая поисковая система искусственного интеллекта с открытым исходным кодом, спонсируемая xAI и Vercel.
boy illustration
Конструкция сервиса Synology Drive сочетает проникновение в интрасеть и синхронизацию папок заметок Obsidian в облаке.
boy illustration
Центр конфигурации————Накос
boy illustration
Начинаем с нуля при разработке в облаке Copilot: начать разработку с минимальным использованием кода стало проще
boy illustration
[Серия Docker] Docker создает мультиплатформенные образы: практика архитектуры Arm64
boy illustration
Обновление новых возможностей coze | Я использовал coze для создания апплета помощника по исправлению домашних заданий по математике
boy illustration
Советы по развертыванию Nginx: практическое создание статических веб-сайтов на облачных серверах
boy illustration
Feiniu fnos использует Docker для развертывания личного блокнота Notepad
boy illustration
Сверточная нейронная сеть VGG реализует классификацию изображений Cifar10 — практический опыт Pytorch
boy illustration
Начало работы с EdgeonePages — новым недорогим решением для хостинга веб-сайтов
boy illustration
[Зона легкого облачного игрового сервера] Управление игровыми архивами
boy illustration
Развертывание SpringCloud-проекта на базе Docker и Docker-Compose