Spring 安全性 + Ajax 会话超时问题

Posted

技术标签:

【中文标题】Spring 安全性 + Ajax 会话超时问题【英文标题】:Spring security + Ajax session timeout issue 【发布时间】:2014-07-17 02:32:33 【问题描述】:

我有一个使用 Spring MVC 构建的应用程序并使用 Spring 安全性进行保护,一堆控制器是 JSON 休息服务,它们都受到保护。我正在使用LoginUrlAuthenticationEntryPoint 来检测 AJAX 请求并在会话超时时发送403 错误代码 - 所有其他请求都会被重定向回登录页面。

下面是 Spring Security XML sn-p 和 authenticationEntryPoint java 类。

问题是会话超时后的第一个 AJAX 请求,Spring 重定向到登录页面并返回登录页面 html,如果我尝试再次执行 AJAX 请求(重定向发生后),authenticationEntryPoint 将执行并返回 HTTP 错误代码 403 .我已经尝试使用这种机制http://distigme.wordpress.com/2012/11/01/ajax-and-spring-security-form-based-login/ 做同样的事情,并且发生了同样的事情(在第一个 AJAX 请求上发生重定向,所有后续 AJAX 请求都返回 HTTP 403)。对于会话超时的 AJAX 请求,我不想重定向到登录页面。

有什么想法吗?

<beans:bean id="authenticationEntryPoint"  class="mojo.ocs.web.AjaxAwareAuthenticationEntryPoint">
    <beans:constructor-arg name="loginUrl" value="/login"/>
</beans:bean>
<!-- ENTRY POINT REF IMPLEMENTATION -->
<http auto-config="true" use-expressions="true" access-denied-page="/accessdenied" entry-point-ref="authenticationEntryPoint">
    <intercept-url pattern="/login" access="isAnonymous()"/>
    <intercept-url pattern="/loginfailed" access="isAnonymous()"/>
    <intercept-url pattern="/welcome" access="isAuthenticated()" />
    <intercept-url pattern="/" access="isAuthenticated()" />
    <intercept-url pattern="/private_res/**" access="isAuthenticated()" />
    <intercept-url pattern="/tne/**" access="isAuthenticated()" />
    <intercept-url pattern="/team_reports/**" access="isAuthenticated()" />
    <form-login login-page="/login" default-target-url="/welcome" always-use-default-target="true" authentication-failure-url="/loginfailed" />
    <logout delete-cookies="JSESSIONID"  logout-success-url="/logout" invalidate-session="true"/>
    <session-management invalid-session-url="/login" />
</http>

这里是 LoginAuthenticationEntryPoint:

public class AjaxAwareAuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint     

    public AjaxAwareAuthenticationEntryPoint(String loginUrl) 
        super(loginUrl);
    

    @Override
    public void commence(
        HttpServletRequest request,
        HttpServletResponse response,
        AuthenticationException authException)
        throws IOException, ServletException 
        String ajaxHeader = ((HttpServletRequest) request).getHeader("X-Requested-With");
        boolean isAjax = "XMLHttpRequest".equals(ajaxHeader);
        if (isAjax) 
            response.sendError(HttpServletResponse.SC_FORBIDDEN, "Ajax REquest Denied (Session Expired)");
         else 
            super.commence(request, response, authException);
        
    

【问题讨论】:

【参考方案1】:

确认这也与 Spring Boot 结合使用 Spring 安全性以编程方式设置安全性而无需任何必需的 XML 也可以正常工作,例如:

@Override
protected void configure(HttpSecurity http) throws Exception 
    http
        .authorizeRequests()
            .antMatchers("/admin**").hasRole("ADMIN")
            // everything else
            .anyRequest().fullyAuthenticated()
        .and()
            .exceptionHandling().authenticationEntryPoint(new AjaxAwareAuthenticationEntryPoint("/login"));

【讨论】:

这对我来说很好用。另外我想添加一个链接到一个完整的例子:gist.github.com/jecyhw/f9f65185dd5d4b284ce4e755637475c7【参考方案2】:

我知道这是很久以前的事了,但我想把它放在这里看看它是否对其他人有帮助。

我在http://distigme.wordpress.com/2012/11/01/ajax-and-spring-security-form-based-login/ 中遵循了相同的想法,并且遇到了相同的问题,即第一个返回的内容是登录页面,然后是 HTTP 403。

我认为这是 Spring 的一部分,我们在 Spring XML 配置做所有事情之间进行了分割,或者我们编写了一堆代码来重载它可以为我们做的事情。我更喜欢在 XML 配置中做尽可能多的事情。

我的解决方案是让 XML 配置像博客一样抛出 403 错误。我没有写Matching 类,因为我的工作流程需要回到第一页,所以我不使用org.springframework.security.web.savedrequest.HttpSessionRequestCache

<bean id="loginUrlAuthenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
    <constructor-arg name="loginFormUrl" value="/index.html" />
</bean>

<bean id="authenticationEntryPoint" class="org.springframework.security.web.authentication.DelegatingAuthenticationEntryPoint">
    <constructor-arg>
        <map>
            <entry key="!hasHeader('X-Requested-With','XMLHttpRequest')" value-ref="loginUrlAuthenticationEntryPoint" />
        </map>
    </constructor-arg>
    <property name="defaultEntryPoint">
        <bean class="org.springframework.security.web.authentication.Http403ForbiddenEntryPoint" />
    </property>
</bean>

如果我在其他地方不需要嵌套豆子,我非常喜欢嵌套豆子。在我的$.ajax 电话中,我放了

dataType: 'json'

确保如果返回的内容不是 JSON(例如登录页面),则调用 error 函数。这也会捕获 403 错误。

error: function (xhr, textStatus, errorThrown) 
    if (xhr.status == 403 || textStatus == 'parsererror' && xhr.responseText.match('rememberMe').length > 0) 
        alert('Your session has timed out.');
        window.location = '<c:url value="/index.html" />';
     else
        alert('Something went wrong. ' + xhr.status + ': ' + errorThrown);


我正在搜索 rememberMe 文本以确保它是登录页面。我不希望在任何其他页面上出现这种情况。

【讨论】:

【参考方案3】:

我通过实现自己的自定义过滤器解决了这个问题,将它放在 ANONYMOUS_FILTER 之前,如果 Spring 主体不存在则返回 403。

【讨论】:

以上是关于Spring 安全性 + Ajax 会话超时问题的主要内容,如果未能解决你的问题,请参考以下文章

ajax 调用无法识别 Spring Security 会话超时

Spring Web 应用程序中带有 Ajax 轮询的会话超时

Spring 3.1:处理会话超时

如何使用 Spring 安全性获取会话超时消息

会话超时后,Spring 安全性不会重定向到上次请求的页面登录

Grails,在 AJAX 调用中处理会话超时