Как обрабатывать истекший сеанс с помощью Spring Security и jQuery? - PullRequest
23 голосов
/ 27 июля 2010

Я использую Spring Security и jQuery в своем приложении.Главная страница использует загрузку содержимого динамически во вкладки через AJAX.И все в порядке, однако иногда у меня есть страница входа в систему на моей вкладке, и если я введу учетные данные, я буду перенаправлен на страницу содержимого без вкладок.

Так что я бы хотел справиться с этой ситуацией.Я знаю, что некоторые люди используют AJAX-аутентификацию, но я не уверен, что она подходит для меня, потому что она выглядит довольно сложной для меня, и мое приложение не разрешает доступ без входа в систему ранее.Я хотел бы просто написать глобальный обработчик для всех ответов AJAX, который будет делать window.location.reload(), если нам потребуется аутентификация.Я думаю, что в этом случае лучше получить ошибку 401 вместо стандартной формы входа, поскольку ее легче обрабатывать.

Итак,

1) Можно ли написать глобальную ошибкуобработчик для всех запросов jQuery AJAX?

2) Как настроить поведение Spring Security для отправки ошибки 401 для запросов AJAX, но для обычных запросов, чтобы обычная страница входа отображалась как обычно?

3) Может быть, у вас есть более изящное решение?Пожалуйста, поделитесь им.

Спасибо.

Ответы [ 7 ]

10 голосов
/ 19 июня 2012

Вот подход, который я считаю довольно простым. Это комбинация подходов, которые я наблюдал на этом сайте. Я написал в блоге об этом: http://yoyar.com/blog/2012/06/dealing-with-the-spring-security-ajax-session-timeout-problem/

Основная идея состоит в том, чтобы использовать префикс api url (то есть / api / secured), как предложено выше, вместе с точкой входа аутентификации. Все просто и работает.

Вот точка входа аутентификации:

package com.yoyar.yaya.config;

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;

import javax.servlet.ServletException;
import javax.servlet.http.*;
import java.io.IOException;

public class AjaxAwareAuthenticationEntryPoint 
             extends LoginUrlAuthenticationEntryPoint {

    public AjaxAwareAuthenticationEntryPoint(String loginUrl) {
        super(loginUrl);
    }

    @Override
    public void commence(
        HttpServletRequest request, 
        HttpServletResponse response, 
        AuthenticationException authException) 
            throws IOException, ServletException {

        boolean isAjax 
            = request.getRequestURI().startsWith("/api/secured");

        if (isAjax) {
            response.sendError(403, "Forbidden");
        } else {
            super.commence(request, response, authException);
        }
    }
}

А вот что происходит в вашем весеннем контексте: xml:

<bean id="authenticationEntryPoint"
  class="com.yoyar.yaya.config.AjaxAwareAuthenticationEntryPoint">
    <constructor-arg name="loginUrl" value="/login"/>
</bean>

<security:http auto-config="true"
  use-expressions="true"
  entry-point-ref="authenticationEntryPoint">
    <security:intercept-url pattern="/api/secured/**" access="hasRole('ROLE_USER')"/>
    <security:intercept-url pattern="/login" access="permitAll"/>
    <security:intercept-url pattern="/logout" access="permitAll"/>
    <security:intercept-url pattern="/denied" access="hasRole('ROLE_USER')"/>
    <security:intercept-url pattern="/" access="permitAll"/>
    <security:form-login login-page="/login"
                         authentication-failure-url="/loginfailed"
                         default-target-url="/login/success"/>
    <security:access-denied-handler error-page="/denied"/>
    <security:logout invalidate-session="true"
                     logout-success-url="/logout/success"
                     logout-url="/logout"/>
</security:http>
9 голосов
/ 03 сентября 2012

Я использовал следующее решение.

В весенней безопасности определен недопустимый URL-адрес сеанса

<security:session-management invalid-session-url="/invalidate.do"/>

Для этой страницы добавлен следующий контроллер

@Controller
public class InvalidateSession
{
    /**
     * This url gets invoked when spring security invalidates session (ie timeout).
     * Specific content indicates ui layer that session has been invalidated and page should be redirected to logout. 
     */
    @RequestMapping(value = "invalidate.do", method = RequestMethod.GET)
    @ResponseBody
    public String invalidateSession() {
        return "invalidSession";
    }
}

А для ajax использовал ajaxSetup для обработки всех запросов ajax:

// Checks, if data indicates that session has been invalidated.
// If session is invalidated, page is redirected to logout
   $.ajaxSetup({
    complete: function(xhr, status) {
                if (xhr.responseText == 'invalidSession') {
                    if ($("#colorbox").count > 0) {
                        $("#colorbox").destroy();
                    }
                    window.location = "logout";
                }
            }
        });
4 голосов
/ 15 января 2011

Взгляните на http://forum.springsource.org/showthread.php?t=95881, Я думаю, что предлагаемое решение намного яснее, чем другие ответы здесь:

  1. Добавьте пользовательский заголовок в ваши jjery ajax-вызовы (используя хук beforeSend). Вы также можете использовать заголовок «X-Requested-With», который отправляет jQuery.
  2. Настройте Spring Security для поиска этого заголовка на стороне сервера, чтобы он возвращал код ошибки HTTP 401 вместо того, чтобы перевести пользователя на страницу входа в систему.
3 голосов
/ 01 декабря 2010

Я только что нашел решение этой проблемы, но не проверил его полностью. Я также использую Spring, Spring Security и JQuery. Сначала с контроллера моего логина я установил код состояния 401:

LoginController {

public ModelAndView loginHandler(HttpServletRequest request, HttpServletResponse response) {

...
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
... 
return new ModelAndView("login", model);
}

В своих методах onload () все мои страницы вызывают функцию из моего глобального файла JavaScript:

function initAjaxErrors() {

jQuery(window).ajaxError(function(event, xmlHttpRequest, ajaxOptions, thrownError) {
    if (403 == xmlHttpRequest.status)
        showMessage("Permission Denied");
    else
        showMessage("An error occurred: "+xmlHttpRequest.status+" "+xmlHttpRequest.statusText);
});

}

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

2 голосов
/ 06 октября 2010

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

$.ajax({ type: 'GET',
    url: GetRootUrl() + '/services/dosomething.ashx',
    success: function (data) {
      if (HasErrors(data)) return;

      // process data returned...

    },
    error: function (xmlHttpRequest, textStatus) {
      ShowStatusFailed(xmlHttpRequest);
    }
  });

И тогда функция HasErrors() выглядит следующим образом и может использоваться всеми страницами.

function HasErrors(data) {
  // check for redirect to login page
  if (data.search(/login\.aspx/i) != -1) {
    top.location.href = GetRootUrl() + '/login.aspx?lo=TimedOut';
    return true;
  }
  // check for IIS error page
  if (data.search(/Internal Server Error/) != -1) {
    ShowStatusFailed('Server Error.');
    return true;
  }
  // check for our custom error handling page
  if (data.search(/Error.aspx/) != -1) {
    ShowStatusFailed('An error occurred on the server. The Technical Support Team has been provided with the error details.');
    return true;
  }
  return false;
}
0 голосов
/ 22 сентября 2014

Когда происходит тайм-аут, пользователь перенаправляется на страницу входа в систему после запуска любого действия ajax, когда сеанс уже очищен

контекст безопасности:

<http use-expressions="true" entry-point-ref="authenticationEntryPoint">
    <logout invalidate-session="true" success-handler-ref="logoutSuccessBean" delete-cookies="JSESSIONID" />
    <custom-filter position="CONCURRENT_SESSION_FILTER" ref="concurrencyFilter" />
    <custom-filter position="FORM_LOGIN_FILTER" ref="authFilter" />
    <session-management invalid-session-url="/logout.xhtml" session-authentication-strategy-ref="sas"/>
</http>

<beans:bean id="concurrencyFilter"
  class="org.springframework.security.web.session.ConcurrentSessionFilter">
    <beans:property name="sessionRegistry" ref="sessionRegistry" />
    <beans:property name="expiredUrl" value="/logout.xhtml" />
</beans:bean>

<beans:bean id="authenticationEntryPoint"  class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
    <beans:property name="loginFormUrl" value="/login.xhtml" />
</beans:bean>

<beans:bean id="authFilter"
  class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
    <beans:property name="sessionAuthenticationStrategy" ref="sas" />
    <beans:property name="authenticationManager" ref="authenticationManager" />
    <beans:property name="authenticationSuccessHandler" ref="authenticationSuccessBean" />
    <beans:property name="authenticationFailureHandler" ref="authenticationFailureBean" />
</beans:bean>

<beans:bean id="sas" class="org.springframework.security.web.authentication.session.ConcurrentSessionControlStrategy">
    <beans:constructor-arg name="sessionRegistry" ref="sessionRegistry" />
    <beans:property name="maximumSessions" value="1" />
    <beans:property name="exceptionIfMaximumExceeded" value="1" />
</beans:bean>

Логин слушателя:

public class LoginListener implements PhaseListener {

@Override
public PhaseId getPhaseId() {
    return PhaseId.RESTORE_VIEW;
}

@Override
public void beforePhase(PhaseEvent event) {
    // do nothing
}

@Override
public void afterPhase(PhaseEvent event) {
    FacesContext context = event.getFacesContext();
    HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest();
    String logoutURL = request.getContextPath() + "/logout.xhtml";
    String loginURL = request.getContextPath() + "/login.xhtml";

    if (logoutURL.equals(request.getRequestURI())) {
        try {
            context.getExternalContext().redirect(loginURL);
        } catch (IOException e) {
            throw new FacesException(e);
        }
    }
}
*

} * 1013

0 голосов
/ 04 июня 2014

Итак, здесь есть 2 проблемы. 1) Безопасность Spring работает, но ответ возвращается в браузер в виде ajax-вызова. 2) Spring Security отслеживает изначально запрошенную страницу, чтобы она могла перенаправить вас на нее ПОСЛЕ входа в систему (если вы не укажете, что всегда хотите использовать определенную страницу после входа в систему). В этом случае запрос был строкой Ajax, поэтому вы будете перенаправлены на эту строку, и именно это вы увидите в браузере.

Простым решением является обнаружение ошибки Ajax, и если отправленный запрос относится только к вашей странице входа в систему (Spring отправит обратно страницу входа html, это будет свойство responseText запроса), обнаружит ее. Затем просто перезагрузите текущую страницу, которая удалит пользователя из контекста вызова Ajax. Spring затем автоматически отправит их на страницу входа. (Я использую j_username по умолчанию, которое является строковым значением, уникальным для моей страницы входа).

$(document).ajaxError( function(event, request, settings, exception) {
    if(String.prototype.indexOf.call(request.responseText, "j_username") != -1) {
        window.location.reload(document.URL);
    }
});
...