Spring Security – Redirect to the Previous URL After Login – Spring Security – 登录后重定向到以前的URL

最后修改: 2017年 3月 1日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

This article will focus on how to redirect a user back to the originally requested URL – after they log in.

本文将重点讨论如何在用户登录后将其重定向到最初请求的URL

Previously, we’ve seen how to redirect to different pages after login with Spring Security for different types of users and covered various types of redirections with Spring MVC.

之前,我们已经看到了如何用Spring Security为不同类型的用户在登录后重定向到不同的页面,并涵盖了各种类型的Spring MVC重定向。

The article is based on top of the Spring Security Login tutorial.

这篇文章是基于Spring安全登录教程的基础上编写的。

2. Common Practice

2.常见的做法

The most common ways to implement redirection logic after login are:

登录后实现重定向逻辑的最常见方式是。

  • using HTTP Referer header
  • saving the original request in the session
  • appending original URL to the redirected login URL

Using the HTTP Referer header is a straightforward way, for most browsers and HTTP clients set Referer automatically. However, as Referer is forgeable and relies on client implementation, using HTTP Referer header to implement redirection is generally not suggested.

使用HTTP Referer是一个直接的方法,因为大多数浏览器和HTTP客户端会自动设置Refer。然而,由于Referer是可伪造的,并且依赖于客户端的实现,所以一般不建议使用HTTP Referer头来实现重定向。

Saving the original request in the session is a safe and robust way to implement this kind of redirect. Besides the original URL, we can store original request attributes and any custom properties in the session.

在会话中保存原始请求是实现这种重定向的一种安全而稳健的方式。除了原始URL,我们还可以在会话中存储原始请求属性和任何自定义属性。

And appending original URL to the redirected login URL is usually seen in SSO implementations. When authenticated via an SSO service, users will be redirected to the originally requested page, with the URL appended. We must ensure the appended URL is properly encoded.

将原始URL附加到重定向的登录URL通常出现在SSO实现中。当通过SSO服务进行认证时,用户将被重定向到最初请求的页面,并附加上URL。我们必须确保附加的URL被正确编码。

Another similar implementation is to put the original request URL in a hidden field inside the login form. But this is no better than using HTTP Referer

另一个类似的实现是把原始请求的URL放在登录表的一个隐藏字段中。但这并不比使用HTTP Referer更好。

In Spring Security, the first two approaches are natively supported.

在Spring Security中,前两种方法得到了原生支持。

It must be noted that for newer versions of Spring Boot, by default, Spring Security is able to redirect after login to the secured resource we tried to access. If we need to always redirect to a specific URL, we can force that through a specific HttpSecurity configuration.

必须注意的是,对于较新版本的Spring Boot,默认情况下,Spring Security能够在登录后重定向到我们试图访问的安全资源。如果我们需要总是重定向到一个特定的URL,我们可以通过一个特定的HttpSecurity配置来强制实现。

3. AuthenticationSuccessHandler

3.AuthenticationSuccessHandler

In form-based authentication, redirection happens right after login, which is handled in an AuthenticationSuccessHandler instance in Spring Security.

在基于表单的认证中,重定向在登录后立即发生,这在Spring SecurityAuthenticationSuccessHandler实例中处理。

Three default implementations are provided: SimpleUrlAuthenticationSuccessHandler, SavedRequestAwareAuthenticationSuccessHandler and ForwardAuthenticationSuccessHandler. We’ll focus on the first two implementations.

提供了三种默认的实现方式。SimpleUrlAuthenticationSuccessHandlerSavedRequestAwareAuthenticationSuccessHandlerForwardAuthenticationSuccessHandler。我们将专注于前两种实现。

3.1. SavedRequestAwareAuthenticationSuccessHandler

3.1. SavedRequestAwareAuthenticationSuccessHandler

SavedRequestAwareAuthenticationSuccessHandler makes use of the saved request stored in the session. After a successful login, users will be redirected to the URL saved in the original request.

SavedRequestAwareAuthenticationSuccessHandler利用保存在会话中的请求。登录成功后,用户将被重定向到原始请求中保存的URL。

For form login, SavedRequestAwareAuthenticationSuccessHandler is used as the default AuthenticationSuccessHandler.

对于表单登录,SavedRequestAwareAuthenticationSuccessHandler被用作默认的AuthenticationSuccessHandler

@Configuration
@EnableWebSecurity
public class RedirectionSecurityConfig {

    //...

    @Override
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
          .authorizeRequests()
          .antMatchers("/login*")
          .permitAll()
          .anyRequest()
          .authenticated()
          .and()
          .formLogin();
        return http.build();
    }
    
}

And the equivalent XML would be:

而对应的XML将是。

<http>
    <intercept-url pattern="/login" access="permitAll"/>
    <intercept-url pattern="/**" access="isAuthenticated()"/>
    <form-login />
</http>

Suppose we have a secured resource at location “/secured”. For the first time access to the resource, we’ll be redirected to the login page; after filling in credentials and posting the login form, we’ll be redirected back to our originally requested resource location:

假设我们有一个位于”/secured “位置的安全资源。在第一次访问该资源时,我们会被重定向到登录页面;在填写凭证和发布登录表格后,我们会被重定向到我们最初请求的资源位置。

@Test
public void givenAccessSecuredResource_whenAuthenticated_thenRedirectedBack() 
  throws Exception {
 
    MockHttpServletRequestBuilder securedResourceAccess = get("/secured");
    MvcResult unauthenticatedResult = mvc
      .perform(securedResourceAccess)
      .andExpect(status().is3xxRedirection())
      .andReturn();

    MockHttpSession session = (MockHttpSession) unauthenticatedResult
      .getRequest()
      .getSession();
    String loginUrl = unauthenticatedResult
      .getResponse()
      .getRedirectedUrl();
    mvc
      .perform(post(loginUrl)
        .param("username", userDetails.getUsername())
        .param("password", userDetails.getPassword())
        .session(session)
        .with(csrf()))
      .andExpect(status().is3xxRedirection())
      .andExpect(redirectedUrlPattern("**/secured"))
      .andReturn();

    mvc
      .perform(securedResourceAccess.session(session))
      .andExpect(status().isOk());
}

3.2. SimpleUrlAuthenticationSuccessHandler

3.2.SimpleUrlAuthenticationSuccessHandler

Compared to the SavedRequestAwareAuthenticationSuccessHandler, SimpleUrlAuthenticationSuccessHandler gives us more options on redirection decisions.

SavedRequestAwareAuthenticationSuccessHandler相比,SimpleUrlAuthenticationSuccessHandler在重定向决策上给了我们更多选择。

We can enable Referer-based redirection by setUserReferer(true):

我们可以通过setUserRefer(true)启用基于Refer的重定向。

public class RefererRedirectionAuthenticationSuccessHandler 
  extends SimpleUrlAuthenticationSuccessHandler
  implements AuthenticationSuccessHandler {

    public RefererRedirectionAuthenticationSuccessHandler() {
        super();
        setUseReferer(true);
    }

}

Then use it as the AuthenticationSuccessHandler in RedirectionSecurityConfig:

然后将其作为AuthenticationSuccessHandlerRedirectionSecurityConfig中使用。

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
      .authorizeRequests()
      .antMatchers("/login*")
      .permitAll()
      .anyRequest()
      .authenticated()
      .and()
      .formLogin()
      .successHandler(new RefererAuthenticationSuccessHandler());
    return http.build(); 
}

And for XML configuration:

而对于XML配置。

<http>
    <intercept-url pattern="/login" access="permitAll"/>
    <intercept-url pattern="/**" access="isAuthenticated()"/>
    <form-login authentication-success-handler-ref="refererHandler" />
</http>

<beans:bean 
  class="RefererRedirectionAuthenticationSuccessHandler" 
  name="refererHandler"/>

3.3. Under the Hood

3.3.引擎盖下

There is no magic in these easy to use features in Spring Security. When a secured resource is being requested, the request will be filtered by a chain of various filters. Authentication principals and permissions will be checked. If the request session is not authenticated yet, AuthenticationException will be thrown.

Spring Security中,这些易于使用的功能并没有什么魔力。当一个安全资源被请求时,该请求将被一连串的各种过滤器过滤。将检查认证原则和权限。如果请求会话尚未被认证,AuthenticationException将被抛出。

The AuthenticationException will be caught in the ExceptionTranslationFilter, in which an authentication process will be commenced, resulting in a redirection to the login page.

AuthenticationException 将在ExceptionTranslationFilter中捕获,其中将开始一个认证过程,导致重定向到登录页面。

public class ExceptionTranslationFilter extends GenericFilterBean {

    //...

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
      throws IOException, ServletException {
        //...

        handleSpringSecurityException(request, response, chain, ase);

        //...
    }

    private void handleSpringSecurityException(HttpServletRequest request,
      HttpServletResponse response, FilterChain chain, RuntimeException exception)
      throws IOException, ServletException {

        if (exception instanceof AuthenticationException) {

            sendStartAuthentication(request, response, chain,
              (AuthenticationException) exception);

        }

        //...
    }

    protected void sendStartAuthentication(HttpServletRequest request,
      HttpServletResponse response, FilterChain chain,
      AuthenticationException reason) throws ServletException, IOException {
       
       SecurityContextHolder.getContext().setAuthentication(null);
       requestCache.saveRequest(request, response);
       authenticationEntryPoint.commence(request, response, reason);
    }

    //... 

}

After login, we can customize behaviors in an AuthenticationSuccessHandler, as shown above.

登录后,我们可以在AuthenticationSuccessHandler中定制行为,如上所示。

4. Conclusion

4.结论

In this Spring Security example, we discussed common practice for redirection after login and explained implementations using Spring Security.

在这个Spring Security例子中,我们讨论了登录后重定向的常见做法,并解释了使用Spring Security的实现。

Note that all the implementations we mentioned are vulnerable to certain attacks if no validation or extra method controls are applied. Users might be redirected to a malicious site by such attacks.

请注意我们提到的所有实现都容易受到某些攻击,如果没有应用验证或额外的方法控制。用户可能会被这种攻击重定向到一个恶意网站。

The OWASP has provided a cheat sheet to help us handle unvalidated redirects and forwards. This would do a lot of help if we need to build implementations on our own.

OWASP提供了一个cheat sheet来帮助我们处理未经验证的重定向和转发。如果我们需要自己建立实现,这将做很大的帮助。

The full implementation code of this article can be found over on Github.

本文的完整实施代码可以在Github上找到over