Как заставить браузер клиента перестать запрашивать истекший идентификатор сеанса? - PullRequest
1 голос
/ 27 сентября 2010

Я занимаюсь разработкой веб-приложения на Java, которое будет работать в защищенной интрасети и не требует входа пользователя. Однако приложение сохраняет состояние разговора в HttpSession. Пользовательский ввод не сохраняется в базе данных, пока он явно не нажмет кнопку сохранения на каком-то этапе диалога. До тех пор их ввод сохраняется в объекте HttpSession. Если их сеанс истекает, пользователь должен быть направлен на страницу, которая информирует его об истечении сеанса.

Это работает нормально, за исключением проблемы с редиректом. Когда пользователь позволяет своему сеансу бездействовать дольше времени, определенного в <session-timeout>, срок сеанса истекает, как и ожидалось. Тем не менее, моя попытка перенаправить пользователя на простую страницу «Ваша сессия истекла», похоже, провалилась. Перенаправление работает нормально, но если пользователь не закроет все открытые окна браузера на своем рабочем столе (не только те, которые были открыты для страницы моего веб-приложения), они будут продолжать перенаправляться на страницу "сеанс истек" навсегда.

Вот мои ограничения:

  • Клиентские рабочие станции используют Internet Explorer. Это в масштабах всей компании и в ближайшее время не изменится.
  • Пользователи будут иметь несколько экземпляров IE, открытых на их рабочем столе, как часть их обычного рабочего процесса. Недопустимо указывать им закрывать все экземпляры IE.
  • Не использовать какие-либо компоненты AJAX в этом веб-приложении

Я реализовал перенаправление с помощью сервлета Java Filter. Вот соответствующие фрагменты кода:

@Override
public void doFilter(
        ServletRequest request, 
        ServletResponse response,
        FilterChain filterChain) 
        throws IOException, ServletException {
    Validate.notNull(filterConfig);
    Validate.isTrue(request instanceof HttpServletRequest);
    HttpServletRequest httpServletRequest = (HttpServletRequest) request;
    String requestedSessionId = httpServletRequest.getRequestedSessionId();
    logger.info("requestedSessionId: " + requestedSessionId);
    HttpSession httpSession = httpServletRequest.getSession(false);

    if (requestedSessionId == null) {
        // No need to do anything here if no session exists yet
        logger.debug("No session exists yet");
        filterChain.doFilter(request, response);
    } else {
        if (httpSession == null) {
            Validate.isTrue(response instanceof HttpServletResponse);
            HttpServletResponse httpServletResponse =
                (HttpServletResponse) response;
            handleSessionExpired(
                httpServletRequest,
                httpServletResponse);
        } else {
            if (logger.isDebugEnabled()) {
                logger.debug("Session OK | requested URL: " + 
                    httpServletRequest.getRequestURL().toString());
                }
                filterChain.doFilter(request, response);
            }
        }
    }
}

private void handleSessionExpired(
        HttpServletRequest httpServletRequest,
        HttpServletResponse httpServletResponse) 
        throws IOException {
    logger.warn("expired session | id: " + 
        httpServletRequest.getRequestedSessionId());
    String expirationPageURL = 
        httpServletRequest.getContextPath() + "/" + 
        "SessionExpiredNotification.html";
    httpServletResponse.sendRedirect(expirationPageURL);
}

Страница SessionExpiredNotification.html должна быть концом строки. Пользователь должен закрыть это окно браузера и открыть новое, если он хочет начать новый разговор. Проблема заключается в том, что в новом окне браузера все еще требуется использовать старое значение идентификатора сеанса, которое было связано с теперь недействительным сеансом, всякий раз, когда у пользователя на рабочем столе открыты какие-либо другие экземпляры Internet Explorer. Это не относится к IE, так как я подтвердил, что Firefox ведет себя точно так же.

Когда этот код будет достигнут в моем Filter:

String requestedSessionId = httpServletRequest.getRequestedSessionId();
logger.info("requestedSessionId: " + requestedSessionId);

Я вижу, что браузер на стороне клиента все еще удерживает старое значение идентификатора сеанса и запрашивает его снова и снова.

Я не уверен, что это уместно, но мой контейнер веб-приложений - Tomcat 6.x.

МОЙ ВОПРОС:
Как веб-приложение сервера может сообщить клиентской рабочей станции, что идентификатор сеанса больше недействителен, так что клиент его отбросит?

Ответы [ 2 ]

2 голосов
/ 27 сентября 2010

Если request.getSession(false) возвращает ноль, вам следует создать новый сеанс. Вы можете сделать это, позвонив request.getSession(true).

Другими словами, ни в каком месте в опубликованном коде вы не указываете контейнеру сервлета создать новый сеанс и назначить ему текущий запрос.

0 голосов
/ 28 сентября 2010

Это решение, которое я использовал.Я не должен был вызывать sendRedirect () в моем фильтре, так как он никогда не вернет новый JSESSIONID в клиентский браузер.Мне нужно отправить реальный ответ, который убивает старый JSESSIONID Cookie, в противном случае клиентский браузер просто будет пытаться его использовать.Моей первой мыслью было получить cookie-файл JSESSIONID из заголовка запроса, установить срок его действия, а затем включить cookie-файл с истекшим сроком действия в ответ, чтобы клиент действовал по истечении срока действия.Другие пользователи Stackoverflow предположили, что это не было чистым решением, поэтому я отказался от этой идеи.

Я заменил свой метод handleSessionExpired () в Filter на использование RequestDispatcher.Это позволит моему Filter отправить запрос на пользовательскую страницу JSP "ваш сеанс истек".В отличие от перенаправления, RequestDispatcher отправит правильный ответ клиенту.

Вот основной метод в моем Filter:

@Override
public void doFilter(
        ServletRequest request, 
        ServletResponse response,
        FilterChain filterChain) 
        throws IOException, ServletException {
    Validate.notNull(filterConfig);
    Validate.isTrue(request instanceof HttpServletRequest);
    HttpServletRequest httpServletRequest =
        (HttpServletRequest) request;
    String requestedSessionId = httpServletRequest.getRequestedSessionId();
    logger.info("requestedSessionId: " + requestedSessionId);
    HttpSession httpSession = httpServletRequest.getSession(false);

    if (requestedSessionId == null) {
        // No need to do anything here if no session exists yet
        logger.debug("No session exists yet");
        filterChain.doFilter(request, response);
    } else {
        if (httpSession == null) {
            Validate.isTrue(response instanceof HttpServletResponse);
            HttpServletResponse httpServletResponse =
                (HttpServletResponse) response;
            handleSessionExpired(
                httpServletRequest,
                httpServletResponse);
        } else {
            filterChain.doFilter(request, response);
        }
    }
}

Мой handleSessionExpired() метод оченьпросто.Этот дополнительный вызов метода существует только из-за другого особого варианта использования, который должен обрабатывать мой фильтр (но он не относится к моему первоначальному вопросу).

private void handleSessionExpired(
        HttpServletRequest httpServletRequest,
        HttpServletResponse httpServletResponse) 
        throws IOException, ServletException {
    logger.info("expired session | id: " + 
        httpServletRequest.getRequestedSessionId());
    sendSessionExpiredResponse(httpServletRequest, httpServletResponse);
}

My sendSessionExpiredResponse() также довольно прост.Вызов getSession() приведет к созданию нового сеанса (поскольку на данный момент уже нет действительного HttpSession) и JSESSIONID, который будет включен в ответ.Это заботится об очистке устаревшего идентификатора сеанса на стороне клиента.Я установил атрибут запроса «isExpired», чтобы JSP об истечении сеанса знал, что нужно отобразить сообщение о том, что сеанс истек.Я также использую ту же страницу JSP, когда пользователь вручную завершает сеанс, поэтому я использую атрибут, чтобы решить, какой текст отображать на странице.

private void sendSessionExpiredResponse(
        HttpServletRequest httpServletRequest,
        HttpServletResponse httpServletResponse) 
        throws IOException, ServletException {
    httpServletRequest.getSession(true); // force valid session to exist
    httpServletRequest.setAttribute("isExpired", true);
    RequestDispatcher rd = filterConfig.getServletContext()
        .getNamedDispatcher("SessionExpired");
    rd.forward(httpServletRequest, httpServletResponse);
}

Вызов getNamedDispatcher() получает JSP черезмоя запись в web.xml:

<servlet>
    <servlet-name>SessionExpired</servlet-name>
    <jsp-file>/SessionExpired.jsp</jsp-file>
</servlet>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...