Handling SAML Redirects on AJAX Requests

2020-07-13 00:03发布

问题:

I have several AngularJS apps all using Spring/Java and SAML 2.0 for SSO (leveraging the Spring Security SAML extension). My SSO id provider is OpenAM and everything is working pretty well. However, I am running into a situation when a user does a global logout from within one application but has other tabs open. Since these are single page web apps, a lot of functionality may still be usable in the orphaned tabs UNTIL, the user does something to invoke an ajax request. Of course, these AJAX requests get intercepted by the Spring Security SAML filters and triggers an authentication attempt via a REDIRECT to the OpenAM login URL. Of course, this wreaks havoc in the browser since redirects to another domain aren't allowed on AJAX requests. Furthermore, I can't really do anything with Angular's $http interceptors as the requests are 'canceled' and no quality information is available in the $http error callback function (such as a convenient 401/403 status code). All I know is that the request failed.

I don't want to assume that all bad $http requests are due to authentication problems (and do a $window.location.reload()) as there could be legitimate reasons for failure. My preference is to suppress the Spring Security redirect (to OpenAM login page) for ajax requests and, instead, send back a 401/403 status code. This would allow me to handle the error in the $http interceptor and do a full page load if it is an authentication failure, thus elegantly redirecting to the login page as if they were going to the site for the first time.

Any ideas for how to accomplish this?

回答1:

The bean responsible for initialization of authentication and decision to return an HTTP error, perform a redirect, ... is an instance of AuthenticationEntryPoint. To change its behavior you can either:

  • customize the current SAMLEntryPoint (extend the commence method) and override the default behavior in case request is an AJAX call from Angular, so it returns an HTTP error instead of performing redirect to IDP
  • or define another security:http element in your Spring context (before the current one) which only covers your AJAX requests (e.g. with attribute pattern="/api/**") and uses an entry point which behaves in the way you want (see Http403ForbiddenEntryPoint)


回答2:

Referring to a possible implementation of Vladimir's first bullet - taken from https://www.jasha.eu/blogposts/2015/10/saml-authentication-angularjs-spring-security.html

public class XhrSamlEntryPoint extends SAMLEntryPoint {

  @Override
  public void commence(HttpServletRequest request, HttpServletResponse response,
                       AuthenticationException e) throws IOException, ServletException {
    if (isXmlHttpRequest(request) && e instanceof InsufficientAuthenticationException) {
      response.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
      return;
    }
    super.commence(request, response, e);
  }

  private boolean isXmlHttpRequest(HttpServletRequest request) {
    return "XMLHttpRequest".equalsIgnoreCase(request.getHeader("X-Requested-With"));
  }
}

Keep in mind that X-Requested-With is not a mandatory header so the detection is not bullet-proof according to this answer. In my case since the backend was used with a SPA frontend, I removed the check of ajax call altogether.