我使用$.post()使用Ajax调用servlet,然后使用生成的HTML片段替换用户当前页面中的div元素。但是,如果会话超时,服务器将发送重定向指令,将用户发送到登录页面。在本例中,jQuery用登录页面的内容替换div元素,迫使用户的眼睛看到一个罕见的场景。
如何使用jQuery1.2.6管理Ajax调用的重定向指令?
我使用$.post()使用Ajax调用servlet,然后使用生成的HTML片段替换用户当前页面中的div元素。但是,如果会话超时,服务器将发送重定向指令,将用户发送到登录页面。在本例中,jQuery用登录页面的内容替换div元素,迫使用户的眼睛看到一个罕见的场景。
如何使用jQuery1.2.6管理Ajax调用的重定向指令?
当前回答
最终实现的解决方案是为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,我们使用该元素替换页面的一部分。此外,我们只需要重定向到登录页面。
其他回答
我有一个适合我的简单解决方案,无需更改服务器代码。。。只要加一茶匙肉豆蔻。。。
$(document).ready(function ()
{
$(document).ajaxSend(
function(event,request,settings)
{
var intercepted_success = settings.success;
settings.success = function( a, b, c )
{
if( request.responseText.indexOf( "<html>" ) > -1 )
window.location = window.location;
else
intercepted_success( a, b, c );
};
});
});
我检查了html标记的存在,但您可以更改indexOf以搜索登录页面中存在的任何唯一字符串。。。
我认为更好的处理方法是利用现有的HTTP协议响应代码,特别是401未授权。
我是这样解决的:
服务器端:如果会话过期,请求为ajax。发送401响应代码头客户端:绑定到ajax事件$('body').bind('ajaxSuccess',函数(事件,请求,设置){if(401==请求状态){window.location='/users/login';}}).bind('ajaxError',函数(事件、请求、设置){if(401==请求状态){window.location='/users/login';}});
IMO这是更通用的,您没有编写一些新的自定义规范/标题。您也不必修改任何现有的ajax调用。
编辑:根据@Rob下面的评论,401(验证错误的HTTP状态代码)应该是指示符。有关详细信息,请参阅403禁止与401未授权HTTP响应。尽管如此,一些web框架使用403来处理身份验证和授权错误,因此进行相应的调整。谢谢Rob。
如果您使用的是Spring Security,答案似乎对人们有用,但我发现扩展LoginUrlAuthenticationEntryPoint并添加特定代码来处理AJAX更为健壮。大多数示例拦截所有重定向,而不仅仅是身份验证失败。这对于我所从事的项目来说是不可取的。如果不希望缓存失败的AJAX请求,您可能会发现还需要扩展ExceptionTranslationFilter并重写“sendStartAuthentication”方法以删除缓存步骤。
示例AjaxAwareAuthenticationEntryPoint:
public class AjaxAwareAuthenticationEntryPoint extends
LoginUrlAuthenticationEntryPoint {
public AjaxAwareAuthenticationEntryPoint(String loginUrl) {
super(loginUrl);
}
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
if (isAjax(request)) {
response.sendError(HttpStatus.UNAUTHORIZED.value(), "Please re-authenticate yourself");
} else {
super.commence(request, response, authException);
}
}
public static boolean isAjax(HttpServletRequest request) {
return request != null && "XMLHttpRequest".equals(request.getHeader("X-Requested-With"));
}
}
来源:1, 2
最终实现的解决方案是为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,我们使用该元素替换页面的一部分。此外,我们只需要重定向到登录页面。
我用@John和@Arpad链接以及@RobWinch链接的答案得到了一份工作解决方案
我使用Spring Security 3.2.9和jQuery 1.10.2。
扩展Spring的类以仅从AJAX请求引起4XX响应:
public class CustomLoginUrlAuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint {
public CustomLoginUrlAuthenticationEntryPoint(final String loginFormUrl) {
super(loginFormUrl);
}
// For AJAX requests for user that isn't logged in, need to return 403 status.
// For normal requests, Spring does a (302) redirect to login.jsp which the browser handles normally.
@Override
public void commence(final HttpServletRequest request,
final HttpServletResponse response,
final AuthenticationException authException)
throws IOException, ServletException {
if ("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Access Denied");
} else {
super.commence(request, response, authException);
}
}
}
applicationContext-security.xml
<security:http auto-config="false" use-expressions="true" entry-point-ref="customAuthEntryPoint" >
<security:form-login login-page='/login.jsp' default-target-url='/index.jsp'
authentication-failure-url="/login.jsp?error=true"
/>
<security:access-denied-handler error-page="/errorPage.jsp"/>
<security:logout logout-success-url="/login.jsp?logout" />
...
<bean id="customAuthEntryPoint" class="com.myapp.utils.CustomLoginUrlAuthenticationEntryPoint" scope="singleton">
<constructor-arg value="/login.jsp" />
</bean>
...
<bean id="requestCache" class="org.springframework.security.web.savedrequest.HttpSessionRequestCache">
<property name="requestMatcher">
<bean class="org.springframework.security.web.util.matcher.NegatedRequestMatcher">
<constructor-arg>
<bean class="org.springframework.security.web.util.matcher.MediaTypeRequestMatcher">
<constructor-arg>
<bean class="org.springframework.web.accept.HeaderContentNegotiationStrategy"/>
</constructor-arg>
<constructor-arg value="#{T(org.springframework.http.MediaType).APPLICATION_JSON}"/>
<property name="useEquals" value="true"/>
</bean>
</constructor-arg>
</bean>
</property>
</bean>
在我的JSP中,添加一个全局AJAX错误处理程序,如下所示
$( document ).ajaxError(function( event, jqxhr, settings, thrownError ) {
if ( jqxhr.status === 403 ) {
window.location = "login.jsp";
} else {
if(thrownError != null) {
alert(thrownError);
} else {
alert("error");
}
}
});
此外,从JSP页面中的AJAX调用中删除现有的错误处理程序:
var str = $("#viewForm").serialize();
$.ajax({
url: "get_mongoDB_doc_versions.do",
type: "post",
data: str,
cache: false,
async: false,
dataType: "json",
success: function(data) { ... },
// error: function (jqXHR, textStatus, errorStr) {
// if(textStatus != null)
// alert(textStatus);
// else if(errorStr != null)
// alert(errorStr);
// else
// alert("error");
// }
});
我希望它能帮助其他人。
更新1我发现我需要将选项(始终使用默认target=“true”)添加到表单登录配置中。这是需要的,因为在AJAX请求被重定向到登录页面后(由于会话过期),Spring会记住之前的AJAX请求,并在登录后自动重定向到它。这将导致返回的JSON显示在浏览器页面上。当然,不是我想要的。
更新2不要总是使用默认target=“true”,而是使用@RobWinch示例来阻止来自requstCache的AJAX请求。这允许正常链接在登录后重定向到其原始目标,但AJAX在登录后会转到主页。