How to manage a redirect request after a jQuery Aj

2018-12-30 23:54发布

I'm using $.post() to call a servlet using Ajax and then using the resulting HTML fragment to replace a div element in the user's current page. However, if the session times out, the server sends a redirect directive to send the user to the login page. In this case, jQuery is replacing the div element with the contents of the login page, forcing the user's eyes to witness a rare scene indeed.

How can I manage a redirect directive from an Ajax call with jQuery 1.2.6?

30条回答
刘海飞了
2楼-- · 2018-12-31 00:03

No browsers handle 301 and 302 responses correctly. And in fact the standard even says they should handle them "transparently" which is a MASSIVE headache for Ajax Library vendors. In Ra-Ajax we were forced into using HTTP response status code 278 (just some "unused" success code) to handle transparently redirects from the server...

This really annoys me, and if someone here have some "pull" in W3C I would appreciate that you could let W3C know that we really need to handle 301 and 302 codes ourselves...! ;)

查看更多
荒废的爱情
3楼-- · 2018-12-31 00:03

in the servlet you should put response.setStatus(response.SC_MOVED_PERMANENTLY); to send the '301' xmlHttp status you need for a redirection...

and in the $.ajax function you should not use the .toString() function..., just

if (xmlHttp.status == 301) { top.location.href = 'xxxx.jsp'; }

the problem is it is not very flexible, you can't decide where you want to redirect..

redirecting through the servlets should be the best way. but i still can not find the right way to do it.

查看更多
千与千寻千般痛.
4楼-- · 2018-12-31 00:07

Some might find the below useful:

I wanted clients to be redirected to the login page for any rest-action that is sent without an authorization token. Since all of my rest-actions are Ajax based, I needed a good generic way to redirect to the login page instead of handling the Ajax success function.

This is what I've done:

On any Ajax request my server will return a Json 200 response "NEED TO AUTHENTICATE" (if the client needs to authenticate).

Simple example in Java (server side):

@Secured
@Provider
@Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter {

    private final Logger m_logger = LoggerFactory.getLogger(AuthenticationFilter.class);

    public static final String COOKIE_NAME = "token_cookie"; 

    @Override
    public void filter(ContainerRequestContext context) throws IOException {        
        // Check if it has a cookie.
        try {
            Map<String, Cookie> cookies = context.getCookies();

            if (!cookies.containsKey(COOKIE_NAME)) {
                m_logger.debug("No cookie set - redirect to login page");
                throw new AuthenticationException();
            }
        }
        catch (AuthenticationException e) {
            context.abortWith(Response.ok("\"NEED TO AUTHENTICATE\"").type("json/application").build());
        }
    }
}

In my Javascript I've added the following code:

$.ajaxPrefilter(function(options, originalOptions, jqXHR) {
    var originalSuccess = options.success;

    options.success = function(data) {
        if (data == "NEED TO AUTHENTICATE") {
            window.location.replace("/login.html");
        }
        else {
            originalSuccess(data);
        }
    };      
});

And that's about it.

查看更多
临风纵饮
5楼-- · 2018-12-31 00:08

I resolved this issue like this:

Add a middleware to process response, if it is a redirect for an ajax request, change the response to a normal response with the redirect url.

class AjaxRedirect(object):
  def process_response(self, request, response):
    if request.is_ajax():
      if type(response) == HttpResponseRedirect:
        r = HttpResponse(json.dumps({'redirect': response['Location']}))
        return r
    return response

Then in ajaxComplete, if the response contains redirect, it must be a redirect, so change the browser's location.

$('body').ajaxComplete(function (e, xhr, settings) {
   if (xhr.status == 200) {
       var redirect = null;
       try {
           redirect = $.parseJSON(xhr.responseText).redirect;
           if (redirect) {
               window.location.href = redirect.replace(/\?.*$/, "?next=" + window.location.pathname);
           }
       } catch (e) {
           return;
       }
   }
}
查看更多
人气声优
6楼-- · 2018-12-31 00:08

Putting together what Vladimir Prudnikov and Thomas Hansen said:

  • Change your server-side code to detect if it's an XHR. If it is, set the response code of the redirect to 278. In django:
   if request.is_ajax():
      response.status_code = 278

This makes the browser treat the response as a success, and hand it to your Javascript.

  • In your JS, make sure the form submission is via Ajax, check the response code and redirect if needed:
$('#my-form').submit(function(event){ 

  event.preventDefault();   
  var options = {
    url: $(this).attr('action'),
    type: 'POST',
    complete: function(response, textStatus) {    
      if (response.status == 278) { 
        window.location = response.getResponseHeader('Location')
      }
      else { ... your code here ... } 
    },
    data: $(this).serialize(),   
  };   
  $.ajax(options); 
});
查看更多
伤终究还是伤i
7楼-- · 2018-12-31 00:08

I got a working solulion using the answers from @John and @Arpad link and @RobWinch link

I use Spring Security 3.2.9 and jQuery 1.10.2.

Extend Spring's class to cause 4XX response only from AJAX requests:

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>

In my JSPs, add a global AJAX error handler as shown here

  $( document ).ajaxError(function( event, jqxhr, settings, thrownError ) {
      if ( jqxhr.status === 403 ) {
          window.location = "login.jsp";
      } else {
          if(thrownError != null) {
              alert(thrownError);
          } else {
              alert("error");
          }
      }
  });

Also, remove existing error handlers from AJAX calls in JSP pages:

        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");
//            }
        });

I hope it helps others.

Update1 I found that I needed to add the option (always-use-default-target="true") to the form-login config. This was needed since after an AJAX request gets redirected to the login page (due to expired session), Spring remembers the previous AJAX request and auto redirects to it after login. This causes the returned JSON to be displayed on the browser page. Of course, not what I want.

Update2 Instead of using always-use-default-target="true", use @RobWinch example of blocking AJAX requests from the requstCache. This allows normal links to be redirected to their original target after login, but AJAX go to the home page after login.

查看更多
登录 后发表回答