Spring Security - перенаправить на предыдущий URL после входа в систему

Spring Security - Перенаправление на предыдущий URL после входа в систему

1. обзор

В этой статье речь пойдет оhow to redirect a user back to the originally requested URL – after they log in.

Ранее мы рассматривалиhow to redirect to different pages after login with Spring Security для разных типов пользователей и охватывали различные типыredirections with Spring MVC.

Статья основана на учебникеSpring Security Login.

2. Обычная практика

Наиболее распространенные способы реализации логики перенаправления после входа в систему:

  • используя заголовокHTTP Referer

  • сохранение исходного запроса в сеансе

  • добавление исходного URL к перенаправленному URL входа

Using the HTTP Referer header - простой способ, для большинства браузеров и клиентовHTTPReferer устанавливается автоматически. Однако, посколькуReferer подделывается и зависит от реализации клиента, использование заголовкаHTTP Referer для реализации перенаправления обычно не рекомендуется.

Saving the original request in the session - это безопасный и надежный способ реализации такого перенаправления. Помимо исходного URL, мы можем хранить оригинальные атрибуты запроса и любые пользовательские свойства в сеансе.

And appending original URL to the redirected login URL обычно встречается в реализациях SSO. При аутентификации через службу единого входа пользователи будут перенаправлены на первоначально запрошенную страницу с добавленным URL-адресом. Мы должны убедиться, что добавленный URL-адрес правильно закодирован.

Другая похожая реализация - поместить исходный URL-адрес запроса в скрытое поле внутри формы входа в систему. Но это не лучше, чем использоватьHTTP Referer

В Spring Security изначально поддерживаются первые два подхода.

3. AuthenticationSuccessHandlerс

При аутентификации на основе форм перенаправление происходит сразу после входа в систему, что обрабатывается в экземпляреAuthenticationSuccessHandler вSpring Security.

Предусмотрены три реализации по умолчанию:SimpleUrlAuthenticationSuccessHandler,SavedRequestAwareAuthenticationSuccessHandler иForwardAuthenticationSuccessHandler. Мы сосредоточимся на первых двух реализациях.

3.1. SavedRequestAwareAuthenticationSuccessHandlerс

SavedRequestAwareAuthenticationSuccessHandler использует сохраненный запрос, хранящийся в сеансе. После успешного входа в систему пользователи будут перенаправлены на URL-адрес, сохраненный в исходном запросе.

Для входа через формуSavedRequestAwareAuthenticationSuccessHandler используется какAuthenticationSuccessHandler по умолчанию.

@Configuration
@EnableWebSecurity
public class RedirectionSecurityConfig extends WebSecurityConfigurerAdapter {

    //...

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
          .authorizeRequests()
          .antMatchers("/login*")
          .permitAll()
          .anyRequest()
          .authenticated()
          .and()
          .formLogin();
    }

}

И эквивалентный XML будет:


    
    
    

Предположим, у нас есть защищенный ресурс в местоположении «/ secure». При первом доступе к ресурсу мы будем перенаправлены на страницу входа; после ввода учетных данных и публикации формы входа мы будем перенаправлены обратно в первоначально запрошенное местоположение ресурса:

@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с

По сравнению сSavedRequestAwareAuthenticationSuccessHandler,SimpleUrlAuthenticationSuccessHandler дает нам больше возможностей при принятии решений о перенаправлении.

Мы можем включить перенаправление на основе Referer наsetUserReferer(true):

public class RefererRedirectionAuthenticationSuccessHandler
  extends SimpleUrlAuthenticationSuccessHandler
  implements AuthenticationSuccessHandler {

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

}

Затем используйте его какAuthenticationSuccessHandler вRedirectionSecurityConfig:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
      .authorizeRequests()
      .antMatchers("/login*")
      .permitAll()
      .anyRequest()
      .authenticated()
      .and()
      .formLogin()
      .successHandler(new RefererAuthenticationSuccessHandler());
}

И для конфигурации XML:


    
    
    


3.3. Под капотом

В этих простых в использовании функциях вSpring Security нет никакого волшебства. Когда запрашивается защищенный ресурс, запрос будет отфильтрован цепочкой различных фильтров. Принципы аутентификации и разрешения будут проверены. Если сеанс запроса еще не аутентифицирован, будет выброшеноAuthenticationException.

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

    //...

}

После входа в систему мы можем настроить поведение вAuthenticationSuccessHandler, как показано выше.

4. Заключение

В этом примереSpring Security мы обсудили обычную практику перенаправления после входа в систему и объяснили реализации с использованием Spring Security.

Обратите внимание, чтоall the implementations we mentioned are vulnerable to certain attacks if no validation or extra method controls are applied. Такие атаки могут перенаправлять пользователей на вредоносный сайт.

OWASP предоставилcheat sheet, чтобы помочь нам обрабатывать непроверенные перенаправления и пересылки. Это очень помогло бы, если бы нам нужно было создавать реализации самостоятельно.

Полный код реализации этой статьи можно найти вover on Github.