1. Обзор
Эта статья будет посвящена тому, как перенаправить пользователя обратно на первоначально запрошенный URL – адрес-после входа в систему .
Ранее мы видели, как перенаправлять на разные страницы после входа в систему с помощью Spring Security для разных типов пользователей, и рассматривали различные типы перенаправлений с помощью Spring MVC .
Эта статья основана на учебнике по входу в систему Spring Security.
2. Обычная практика
Наиболее распространенными способами реализации логики перенаправления после входа в систему являются:
- использование HTTP-реферера заголовка
- сохранение исходного запроса в сеансе
- добавление исходного URL-адреса к перенаправленному URL-адресу входа в систему
Использование HTTP Referer заголовка является простым способом для большинства браузеров и HTTP клиентов автоматически устанавливать Referer . Однако, поскольку Referer поддается подделке и зависит от реализации клиента, использование заголовка HTTP Referer для реализации перенаправления обычно не рекомендуется.
Сохранение исходного запроса в сеансе является безопасным и надежным способом реализации такого рода перенаправления. Помимо исходного URL-адреса, мы можем хранить исходные атрибуты запроса и любые пользовательские свойства в сеансе.
И добавление исходного URL-адреса к перенаправленному URL-адресу входа в систему обычно наблюдается в реализациях единого входа. При проверке подлинности с помощью службы единого входа пользователи будут перенаправлены на первоначально запрошенную страницу с добавленным URL-адресом. Мы должны убедиться, что добавленный URL-адрес правильно закодирован.
Другая аналогичная реализация заключается в том, чтобы поместить исходный URL-адрес запроса в скрытое поле внутри формы входа в систему. Но это не лучше, чем использовать HTTP Referer
В весенней безопасности первые два подхода изначально поддерживаются.
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 будет:
Предположим, у нас есть защищенный ресурс в местоположении “/защищенный”. При первом доступе к ресурсу мы будем перенаправлены на страницу входа в систему; после заполнения учетных данных и публикации формы входа в систему мы будем перенаправлены обратно в первоначально запрошенное местоположение ресурса:
@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 дает нам больше возможностей для принятия решений о перенаправлении.
Мы можем включить перенаправление на основе ссылок с помощью set User Referer(true) :
public class RefererRedirectionAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler implements AuthenticationSuccessHandler { public RefererRedirectionAuthenticationSuccessHandler() { super(); setUseReferer(true); } }
Затем используйте его в качестве AuthenticationSuccessHandler в Конфигурации безопасности перенаправления :
@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.
Обратите внимание, что все реализации, о которых мы упоминали, уязвимы для определенных атак, если не применяется проверка или дополнительные элементы управления методами . В результате таких атак пользователи могут быть перенаправлены на вредоносный сайт.
OWASP предоставил шпаргалку , чтобы помочь нам обрабатывать непроверенные перенаправления и переадресации. Это очень помогло бы, если бы нам нужно было создавать реализации самостоятельно.
Полный код реализации этой статьи можно найти на Github .