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

Spring-在Ajax请求失败后,在成功登录时重定向到链接,ajax,spring,spring-mvc,spring-security,Ajax,Spring,Spring Mvc,Spring Security,我有一个网站,需要在用户操作时在元素内部异步呈现一些HTML。如果用户的会话过期,事情会变得棘手,但可以通过创建自定义的AuthenticationEntryPoint类like和suggest来解决 一旦用户重新登录,我的问题就会出现,因为用户被重定向到请求的最后一个URL,这恰好是Ajax请求,因此我的用户被重定向到HTML片段,而不是它浏览的最后一个页面 我可以通过删除自定义AuthenticationEntryPoint上的会话属性来解决此问题: if (ajaxOrAsync) {

我有一个网站,需要在用户操作时在元素内部异步呈现一些HTML。如果用户的会话过期,事情会变得棘手,但可以通过创建自定义的
AuthenticationEntryPoint
类like和suggest来解决

一旦用户重新登录,我的问题就会出现,因为用户被重定向到请求的最后一个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();
        }

    }
}
前面的代码解决了我的问题,但它是一种非常粗糙的方法来解决我的重定向问题(我克隆并重写了原始请求…+抖动+)

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


我已经看过Spring的,但是我还没有找到一种在Ajax请求失败的情况下将referer url传递给它的方法。

我已经找到了一个可以接受的解决方案,这多亏了在以后浏览另一个页面时提出的一个想法。简言之,我必须创建自己的自定义
ExceptionTranslationFilter
,并且r将
sendStartAuthentication
设置为不保存请求缓存

如果看一下
例外TranslationFilter
代码,它看起来是这样的(对于Finchley SR1):

这使得
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);
    }
}
我的
CustomWebSecurity配置适配器应如下配置:

@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);
    }
}
@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)
            ....;
}

我可以确认一下,这里有各种AJAX请求正在进行,有可能一些AJAX响应会以
HTTP 401 unauthorized
的形式返回吗?如果是这样的话,您现在可以通过一个全局AJAX错误处理函数来解决这个客户端,该函数在
HTTP 401
响应时会弹出某种类型的消息吗成功登录后,是否有可能刷新当前页面?@DavidGoate是的,ajax响应返回为401。将显示一个通知,指示用户未登录。显示再次登录的模式是一个很好的解决方案。我将查看是否可以调整项目要求以适应此解决方案。您将ldn不一定需要遵循模式解决方案,这只是一个建议。也许另一个选择是,在检测到401后,应用程序使用javascript捕获窗口位置,然后使用查询参数(如
https://my-site.com/login?continue={windowLocation}
。然后,您可以让您的登录筛选器在成功身份验证后使用此参数转发到正确的位置,然后使用新的
SimpleLauthenticationSuccessHandler()。setTargetUrlParameter(“继续”)
谢谢@DavidGoate,这听起来也不错。现在我会坚持使用自定义的
例外TranslationFilter
,但如果它不可靠,我会尝试一下你的好建议。=)
@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)
            ....;
}