我使用$.post()使用Ajax调用servlet,然后使用生成的HTML片段替换用户当前页面中的div元素。但是,如果会话超时,服务器将发送重定向指令,将用户发送到登录页面。在本例中,jQuery用登录页面的内容替换div元素,迫使用户的眼睛看到一个罕见的场景。

如何使用jQuery1.2.6管理Ajax调用的重定向指令?


当前回答

大多数给定的解决方案都使用一种变通方法,使用额外的头或不合适的HTTP代码。这些解决方案很可能会奏效,但感觉有点“粗糙”。我想出了另一个解决方案。

我们使用的WIF被配置为在401响应上重定向(passiveRedirectEnabled=“true”)。重定向在处理正常请求时是有用的,但对AJAX请求不起作用(因为浏览器不会执行302/重定向)。

使用global.asax中的以下代码,可以禁用AJAX请求的重定向:

    void WSFederationAuthenticationModule_AuthorizationFailed(object sender, AuthorizationFailedEventArgs e)
    {
        string requestedWithHeader = HttpContext.Current.Request.Headers["X-Requested-With"];

        if (!string.IsNullOrEmpty(requestedWithHeader) && requestedWithHeader.Equals("XMLHttpRequest", StringComparison.OrdinalIgnoreCase))
        {
            e.RedirectToIdentityProvider = false;
        }
    }

这允许您为AJAX请求返回401个响应,然后javascript可以通过重新加载页面来处理这些响应。重新加载页面将抛出一个401,由WIF处理(WIF将用户重定向到登录页面)。

处理401错误的示例javascript:

$(document).ajaxError(function (event, jqxhr, settings, exception) {

    if (jqxhr.status == 401) { //Forbidden, go to login
        //Use a reload, WIF will redirect to Login
        location.reload(true);
    }
});

其他回答

后端弹簧@ExceptionHandler。

400和业务相关异常的错误字符串(将在弹出窗口中显示)302和用于浏览器请求的应用程序异常的错误/登录页面的位置标头(由浏览器自动重定向)500/400和错误/登录页面的位置头,用于通过ajax回调重定向ajax请求

通过用户会话将异常详细信息传递到错误页

@Order(HIGHEST_PRECEDENCE)
public class ExceptionHandlerAdvise {

    private static Logger logger = LoggerFactory.getLogger(ExceptionHandlerAdvise.class);

    @Autowired
    private UserInfo userInfo;

    @ExceptionHandler(value = Exception.class)
    protected ResponseEntity<Object> handleException(Exception ex, WebRequest request) {
        HttpHeaders headers = new HttpHeaders();
        if (isBusinessException(ex)) {
            logger.warn(getRequestURL(request), ex);
            return new ResponseEntity<>(getUserFriendlyErrorMessage(ex), headers, BAD_REQUEST);
        } else {
            logger.error(getRequestURL(request), ex);
            userInfo.setLastError(ex);
            headers.add("Location", "/euc-portal/fault");
            return new ResponseEntity<>(null, headers, isAjaxRequest(request) ? INTERNAL_SERVER_ERROR : FOUND);
        }
    }
}

private boolean isAjaxRequest(WebRequest request) {
    return request.getHeader("x-requested-with") != null;
}

private String getRequestURL(WebRequest request) {
    if (request instanceof ServletWebRequest) {
        HttpServletRequest servletRequest = ((ServletWebRequest) request).getRequest();
        StringBuilder uri = new StringBuilder(servletRequest.getRequestURI());
        if (servletRequest.getQueryString() != null) {
            uri.append("?");
            uri.append(servletRequest.getQueryString());
        }
        return uri.toString();
    }
    return request.getContextPath();
}

登录手柄接口

@Service
public class LoginHandlerInterceptor implements HandlerInterceptor {

    @Autowired
    private UserInfo userInfo;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (userInfo.getPrincipal() == null && !(request.getRequestURI().contains(LOGIN_URL) || request.getRequestURI().contains(FAULT_URL) || request.getRequestURI().startsWith("/app/css"))) {
            response.addHeader("Location", LOGIN_URL);
            response.setStatus(isAjaxRequest(request) ? BAD_REQUEST.value() : FOUND.value());
            return false;
        }
        return true;
    }
}

客户端代码

$.post('/app/request', params).done(function(response) {
    ...
}).fail(function(response) {
    if (response.getResponseHeader('Location')) {
        window.top.location.href = response.getResponseHeader('Location');
        return;
    }
    alert(response);
});

最终实现的解决方案是为Ajax调用的回调函数使用包装器,并在该包装器中检查返回的HTML块上是否存在特定元素。如果找到元素,则包装器执行重定向。如果没有,包装器将调用转发给实际的回调函数。

例如,我们的包装器函数类似于:

function cbWrapper(data, funct){
    if($("#myForm", data).length > 0)
        top.location.href="login.htm";//redirection
    else
        funct(data);
}

然后,在进行Ajax调用时,我们使用了类似的方法:

$.post("myAjaxHandler", 
       {
        param1: foo,
        param2: bar
       },
       function(data){
           cbWrapper(data, myActualCB);
       }, 
       "html"
);

这对我们很有用,因为所有Ajax调用都会在DIV元素中返回HTML,我们使用该元素替换页面的一部分。此外,我们只需要重定向到登录页面。

让我再次引用@Steg描述的问题

我有一个和你类似的问题。我执行一个ajax请求可能的响应:将浏览器重定向到新页面将当前页面上的现有HTML表单替换为新的一

IMHO这是一个真正的挑战,必须正式扩展到当前的HTTP标准。

我相信新的Http标准将使用新的状态代码。意思:目前301/302告诉浏览器去把这个请求的内容取到一个新的位置。

在扩展标准中,它会说如果响应状态为:308(只是一个示例),那么浏览器应该将主页重定向到提供的位置。

话虽如此;我倾向于模仿这种未来的行为,因此当需要document.redirect时,我让服务器响应如下:

status: 204 No Content
x-status: 308 Document Redirect
x-location: /login.html

当JS获得“status:204”时,它检查x-status:308标头的存在,并将document.redirect指向位置标头中提供的页面。

这对你有意义吗?

我只想锁定整个页面的任何ajax请求@SuperG让我开始了。以下是我的结论:

// redirect ajax requests that are redirected, not found (404), or forbidden (403.)
$('body').bind('ajaxComplete', function(event,request,settings){
        switch(request.status) {
            case 301: case 404: case 403:                    
                window.location.replace("http://mysite.tld/login");
                break;
        }
});

我想特别检查某些http状态代码以作为我的决定的基础。然而,您可以绑定到ajaxError以获得除成功之外的任何结果(可能只有200?)

$('body').bind('ajaxError', function(event,request,settings){
    window.location.replace("http://mysite.tld/login");
}

我知道这个话题已经过时了,但我将给出另一种我已经发现并在这里描述过的方法。基本上,我使用的是ASP.MVC和WIF(但这对于本主题的上下文来说并不重要——无论使用哪种框架,答案都是足够的。线索保持不变——在执行ajax请求时处理与身份验证失败相关的问题)。

下面显示的方法可以应用于所有开箱即用的ajax请求(如果它们显然没有重新定义beforeEnd事件)。

$.ajaxSetup({
    beforeSend: checkPulse,
    error: function (XMLHttpRequest, textStatus, errorThrown) {
        document.open();
        document.write(XMLHttpRequest.responseText);
        document.close();
    }
});

在执行任何ajax请求之前,调用CheckPulse方法(可以是任何最简单的控制器方法):

[Authorize]
public virtual void CheckPulse() {}

如果用户未经身份验证(令牌已过期),则无法访问该方法(受Authorize属性保护)。因为框架在令牌过期时处理身份验证,所以它将http状态302置于响应中。如果您不希望浏览器透明地处理302响应,请在Global.asax中捕获它,并将响应状态更改为200 OK。此外,添加header,它指示您以特殊方式处理此类响应(稍后在客户端):

protected void Application_EndRequest()
{
    if (Context.Response.StatusCode == 302
        && (new HttpContextWrapper(Context)).Request.IsAjaxRequest())
    {                
        Context.Response.StatusCode = 200;
        Context.Response.AddHeader("REQUIRES_AUTH", "1");
    }
}

最后在客户端检查这样的自定义头。如果存在,则应该完成到登录页面的完全重定向(在我的情况下,window.location被来自请求的url替换,该url由我的框架自动处理)。

function checkPulse(XMLHttpRequest) {
    var location = window.location.href;
    $.ajax({
        url: "/Controller/CheckPulse",
        type: 'GET',
        async: false,
        beforeSend: null,
        success:
            function (result, textStatus, xhr) {
                if (xhr.getResponseHeader('REQUIRES_AUTH') === '1') {
                    XMLHttpRequest.abort(); // terminate further ajax execution
                    window.location = location;
                }
            }
    });
}