spring-security 如何使用Spring Security和jQuery处理过期的会话?

p5fdfcr1  于 2022-11-11  发布在  Spring
关注(0)|答案(7)|浏览(246)

我在我的应用程序中使用Spring Security和jQuery。主页面通过 AJAX 将内容动态加载到选项卡中。一切都很好,但是有时候我的选项卡中会有登录页面,如果我输入凭据,我会被重定向到没有选项卡的内容页面。
所以我想处理这种情况。我知道有些人使用 AJAX 身份验证,但我不确定它是否适合我,因为它看起来很复杂,而且我应用程序不允许在没有登录之前进行任何访问。我想为所有 AJAX 响应编写一个全局处理程序,如果需要进行身份验证,它将执行window.location.reload()。我认为在这种情况下,"最好是得到401错误而不是标准的登录表单,因为它更容易处理。
所以,
1)是否可以为所有jQuery AJAX 请求编写 * 全局错误处理程序 *?
2)我如何定制Spring Security的行为,使其对 AJAX 请求发送401错误,而对常规请求发送401错误,以便照常显示标准登录页面?
3)也许你有更优雅的解决方案?请分享它。

  • 谢谢-谢谢
bvpmtnay

bvpmtnay1#

这里有一个我认为非常简单的方法。它是我在这个网站上观察到的方法的组合。我写了一篇关于它的博客文章: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);
        }
    }
}

下面是Spring上下文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>
dxpyg8gm

dxpyg8gm2#

我使用了以下解决方案。
在Spring Security中定义了无效的会话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";
                }
            }
        });
pvcm50d1

pvcm50d13#

看一看http://forum.springsource.org/showthread.php?t=95881,我认为建议的解决方案比这里的其他答案要清楚得多:
1.在jquery AJAX 调用中添加一个自定义的头(使用'beforeSend'钩子)。你也可以使用jQuery发送的X-Requested-With头。
1.配置SpringSecurity在服务器端查找该标头,以返回HTTP401错误代码,而不是将用户带到登录页面。

yacmzcpb

yacmzcpb4#

我刚刚想出了一个解决这个问题的方法,但是还没有彻底测试它。我还使用了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);
});

}
在一个项目中,我通过在包含登录表单的iframe周围放置一个jQuery对话框来处理jQuery身份验证。

vxbzzdmp

vxbzzdmp5#

我通常是这样做的:在每次 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;
}
ycggw6v2

ycggw6v26#

因此,这里有两个问题:1)Spring安全性工作正常,但是响应通过 AJAX 调用返回到浏览器。2)Spring安全性跟踪最初请求的页面,以便在您登录后将您重定向到该页面(除非您指定在登录后总是要使用某个页面)。在本例中,请求是一个 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);
    }
});
y0u0uwnf

y0u0uwnf7#

发生超时时,在会话已清除的情况下触发任何 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);
        }
    }
}

}

相关问题