Перейти к основному содержимому

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

· 4 мин. чтения

1. Обзор

В этой статье основное внимание будет уделено тому, как перенаправить пользователя обратно на первоначально запрошенный URL-адрес после того, как он войдет в систему .

Ранее мы видели, как перенаправлять на разные страницы после входа в систему с помощью Spring Security для разных типов пользователей, и рассматривали различные типы перенаправлений с помощью Spring MVC .

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

2. Общепринятая практика

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

  • с использованием заголовка HTTP Referer
  • сохранение исходного запроса в сеансе
  • добавление исходного URL-адреса к перенаправленному URL-адресу входа

Использование заголовка HTTP Referer является простым способом, поскольку большинство браузеров и HTTP - клиентов устанавливают Referer автоматически. Однако, поскольку Referer можно подделать и он зависит от реализации клиента, использование заголовка HTTP Referer для реализации перенаправления обычно не рекомендуется.

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

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

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

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

3. Обработчик успеха аутентификации

При аутентификации на основе форм перенаправление происходит сразу после входа в систему, что обрабатывается экземпляром 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 будет:

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

Предположим, у нас есть защищенный ресурс по адресу «/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

По сравнению с 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:

<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. Под капотом

В этих простых в использовании функциях 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.

Обратите внимание, что все упомянутые нами реализации уязвимы для определенных атак, если не применяются проверки или дополнительные элементы управления методами . В результате таких атак пользователи могут быть перенаправлены на вредоносный сайт.

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

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