Spring Security登录超时,angular ajax请求出错自动跳转至登录页,jQuery也适用

  公司开发采用Spring Security+AngualerJS框架,在session过期之后,ajax请求会直接出错。本文介绍如何实现出错情况下自动跳转至登录页。

  整体思路是,session过期后,ajax请求返回401 unauthentication错误,前端对$http服务添加拦截器,对401错误进行跳转处理,跳转至登录页。

  由于session过期,需要验证的请求(不论是不是ajax请求)会返回302重定向,我们先配置spring security使之能对ajax请求返回401错误。如下:

  实现自定义的RequestMatcher,当请求是ajax请求即匹配上(angular默认不会带上X-Requested-With,这里通过Accept进行判断,也可以在前端对ajax请求添加X-Requested-With头):

public static class AjaxRequestMatcher implements RequestMatcher {
    @Override
    public boolean matches(HttpServletRequest request) {
        return "XMLHttpRequest".equals(request.getHeader("X-Requested-With")) ||
                request.getHeader("Accept") != null && 
                    request.getHeader("Accept").contains("application/json");
    }
}

  实现自定义的AuthenticationEntryPoint,返回401错误:

@Component
public class AjaxAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
            AuthenticationException authException) throws IOException, ServletException {
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
    }

}

  配置错误处理,对ajax请求使用AjaxAuthenticationEntryPoint(

  .exceptionHandling()

  .defaultAuthenticationEntryPointFor(authenticationEntryPoint, new AjaxRequestMatcher())):

    @Autowired
    private AuthenticationEntryPoint authenticationEntryPoint;
    
    protected void configure(HttpSecurity http) throws Exception {
        http
            .headers()
                .cacheControl()
                .and()
            .authorizeRequests()
                .antMatchers(
                    "/login",
                    "/css/**",
                    "/img/**",
                    "/js/**",
                    "/partial/**",
                    "/script/**",
                    "/upload/**",
                    "/plugin/**").permitAll()
                .antMatchers("/**")
                .authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .permitAll()
                .defaultSuccessUrl("/app.html", true)
                .and()
            .logout()
                .logoutUrl("/logout")
                .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
                .logoutSuccessUrl("/login")
                .and()
            .exceptionHandling()
                .defaultAuthenticationEntryPointFor(authenticationEntryPoint, new AjaxRequestMatcher())
                .and()
            .csrf().disable();
    }

  前端,添加拦截器:

angular.module('app', [])
.config(function($httpProvider) {
    $httpProvider.interceptors.push(function($q, $window) {
          return {
            // optional method
            'request': function(config) {
              // do something on success
              return config;
            },
            // optional method
           'requestError': function(rejection) {
              // do something on error
              if (canRecover(rejection)) {
                return responseOrNewPromise
              }
              return $q.reject(rejection);
            },
            // optional method
            'response': function(response) {
              // do something on success
              return response;
            },
            // optional method
           'responseError': function(rejection) {
              // do something on error
              if (rejection.status === 401) {
//                return responseOrNewPromise
                  console.log('401');
                  $window.location.href = 'login?expired';
              }
              return $q.reject(rejection);
            }
          };
        });
});