Spring-在Ajax请求失败后成功登录后重定向到链接

时间:2018-10-26 22:04:07

标签: ajax spring spring-mvc spring-security

我有一个网站,该网站需要在用户操作时异步在元素内呈现一些HTML。如果用户的会话过期,事情会变得棘手,但是可以通过创建自定义的AuthenticationEntryPoint类(例如this SO questionthis SO question建议)来解决。

我的问题出在用户重新登录后,因为用户被重定向到所请求的最后一个URL(恰好是Ajax请求),因此我的用户被重定向到HTML的片段而不是最后一个页面它浏览了。

我能够通过删除自定义AuthenticationEntryPoint上的会话属性来解决此问题:

if (ajaxOrAsync) {
    request.getSession().removeAttribute("SPRING_SECURITY_SAVED_REQUEST");
}

这是我的问题所在。

虽然前面的代码解决了我的问题,但是它的副作用是将用户重定向到主页,而不是浏览的最后一页(因为没有保存的请求)。不会有太大问题,但是它会使网站不一致,因为如果最后一个请求是异步请求,它将被重定向到主页,但是如果是正常请求,它将被重定向到最后浏览的页面。 =(

我设法对此进行了编码以处理这种情况:

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.PortResolver;
import org.springframework.security.web.PortResolverImpl;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.savedrequest.DefaultSavedRequest;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;

import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
import static org.apache.commons.lang.StringUtils.isBlank;

public class CustomAuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint {
    ... // Some not so relevant code

    @Override
    public void commence(final HttpServletRequest request,
                         final HttpServletResponse response,
                         final AuthenticationException authException) throws IOException, ServletException {
        ... // some code to determine if the request is an ajax request or an async one
        if (ajaxOrAsync) {
            useRefererAsSavedRequest(request);

            response.sendError(SC_UNAUTHORIZED);
        } else {
            super.commence(request, response, authException);
        }
    }

    private void useRefererAsSavedRequest(final HttpServletRequest request) {
        request.getSession().removeAttribute(SAVED_REQUEST_SESSION_ATTRIBUTE);

        final URL refererUrl = getRefererUrl(request);
        if (refererUrl != null) {
            final HttpServletRequestWrapper newRequest = new CustomHttpServletRequest(request, refererUrl);
            final PortResolver portResolver = new PortResolverImpl();
            final DefaultSavedRequest newSpringSecuritySavedRequest = new DefaultSavedRequest(newRequest, portResolver);

            request.getSession().setAttribute(SAVED_REQUEST_SESSION_ATTRIBUTE, newSpringSecuritySavedRequest);
        }
    }

    private URL getRefererUrl(final HttpServletRequest request) {
        final String referer = request.getHeader("referer");

        if (isBlank(referer)) {
            return null;
        }

        try {
            return new URL(referer);
        } catch (final MalformedURLException exception) {
            return null;
        }
    }

    private class CustomHttpServletRequest extends HttpServletRequestWrapper {
        private URL url;

        public CustomHttpServletRequest(final HttpServletRequest request, final URL url) {
            super(request);
            this.url = url;
        }

        @Override
        public String getRequestURI() {
            return url.getPath();
        }

        @Override
        public StringBuffer getRequestURL() {
            return new StringBuffer(url.toString());
        }

        @Override
        public String getServletPath() {
            return url.getPath();
        }

    }
}

前面的代码解决了我的问题,但这是解决我的重定向问题的一种非常棘手的方法(我克隆并重写了原始请求... + shudders +)。

所以我的问题是,在成功登录后(考虑到我正在使用的条件),还有其他方法可以重写Spring用于重定向用户的链接吗?

我已经看过Spring的AuthenticationSuccessHandler,但是在Ajax请求失败的情况下,我还没有找到一种将引用网址传达给它的方法。

1 个答案:

答案 0 :(得分:1)

由于reading the docs出现了一个想法,后来又浏览了另一个SO answer,因此我找到了可以解决我问题的方法。简而言之,我将不得不创建自己的自定义ExceptionTranslationFilter,并覆盖sendStartAuthentication以免保存请求缓存。

如果看一下ExceptionTranslationFilter代码,它看起来就这样(对于Finchley SR1):

protected void sendStartAuthentication(HttpServletRequest request,
        HttpServletResponse response, FilterChain chain,
        AuthenticationException reason) throws ServletException, IOException {
    SecurityContextHolder.getContext().setAuthentication(null);
    requestCache.saveRequest(request, response); // <--- Look at me
    logger.debug("Calling Authentication entry point.");
    authenticationEntryPoint.commence(request, response, reason);
}

因此,为了不保存来自Ajax请求的数据,我应该实现一个CustomExceptionTranslationFilter,其作用如下:

@Override
protected void sendStartAuthentication(final HttpServletRequest request,
                                       final HttpServletResponse response,
                                       final FilterChain chain,
                                       final AuthenticationException authenticationException) throws ServletException, IOException {
    ... // some code to determine if the request is an ajax request or an async one
    if (isAjaxOrAsyncRequest) {
        SecurityContextHolder.getContext().setAuthentication(null);

        authenticationEntryPoint.commence(request, response, authenticationException);
    } else {
        super.sendStartAuthentication(request, response, chain, authenticationException);
    }
}

这使CustomAuthenticationEntryPoint的逻辑更加简单:

@Override
public void commence(final HttpServletRequest request,
                     final HttpServletResponse response,
                     final AuthenticationException authException) throws IOException, ServletException {
    ... // some code to determine if the request is an ajax request or an async one, again
    if (isAjaxOrAsyncRequest) {
        response.sendError(SC_UNAUTHORIZED);
    } else {
        super.commence(request, response, authException);
    }
}

我的CustomWebSecurityConfigurerAdapter应该这样配置:

@Override
protected void configure(final HttpSecurity http) throws Exception {
    final CustomAuthenticationEntryPoint customAuthenticationEntryPoint =
            new CustomAuthenticationEntryPoint("/login-path");

    final CustomExceptionTranslationFilter customExceptionTranslationFilter =
            new CustomExceptionTranslationFilter(customAuthenticationEntryPoint);

    http.addFilterAfter(customExceptionTranslationFilter, ExceptionTranslationFilter.class)
            ....
            .permitAll()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .and()
            .exceptionHandling()
            .authenticationEntryPoint(customAuthenticationEntryPoint)
            ....;
}