1. 개요
이 도움말에서는 사용자가 로그인한 후 원래 요청한 URL로 다시 리디렉션하는 방법 에 대해 중점적으로 설명합니다 .
이전에는 다양한 유형의 사용자를 위해 Spring Security 로 로그인한 후 다른 페이지로 리디렉션하는 방법을 살펴보고 Spring MVC 로 다양한 유형의 리디렉션을 다루었습니다 .
이 기사는 Spring Security Login 예제의 상단을 기반으로 합니다.
2. 관례
로그인 후 리디렉션 논리를 구현하는 가장 일반적인 방법은 다음과 같습니다.
- HTTP 리퍼러 헤더 사용
- 세션에 원래 요청 저장
- 리디렉션된 로그인 URL에 원래 URL 추가
HTTP Referer 헤더 를 사용하는 것은 대부분의 브라우저와 HTTP 클라이언트 에서 Referer 를 자동으로 설정하는 간단한 방법입니다. 그러나 Referer 는 위조 가능하고 클라이언트 구현에 의존 하므로 HTTP Referer 헤더를 사용하여 리디렉션을 구현하는 것은 일반적으로 권장되지 않습니다.
원래 요청을 세션에 저장하는 것은 이러한 종류의 리디렉션을 구현하는 안전하고 강력한 방법입니다. 원래 URL 외에도 세션에 원래 요청 속성과 모든 사용자 지정 속성을 저장할 수 있습니다.
리디렉션된 로그인 URL에 원래 URL을 추가하는 것은 일반적으로 SSO 구현에서 볼 수 있습니다. SSO 서비스를 통해 인증되면 사용자는 URL이 추가된 원래 요청 페이지로 리디렉션됩니다. 추가된 URL이 올바르게 인코딩되었는지 확인해야 합니다.
또 다른 유사한 구현은 원래 요청 URL을 로그인 양식 내부의 숨겨진 필드에 넣는 것입니다. 그러나 이것은 HTTP Referer 를 사용하는 것보다 낫지 않습니다.
Spring Security에서는 처음 두 가지 접근 방식이 기본적으로 지원됩니다.
최신 버전의 Spring Boot의 경우 기본적으로 Spring Security는 로그인 후 액세스를 시도한 Security 리소스로 리디렉션할 수 있습니다 . 항상 특정 URL로 리디렉션해야 하는 경우 특정 HttpSecurity 구성을 통해 강제로 리디렉션할 수 있습니다.
3. 인증 성공 핸들러
폼 기반 인증에서 리디렉션은 로그인 직후 발생 하며 Spring Security 의 AuthenticationSuccessHandler 인스턴스에서 처리됩니다 .
SimpleUrlAuthenticationSuccessHandler , SavedRequestAwareAuthenticationSuccessHandler 및 ForwardAuthenticationSuccessHandler 의 세 가지 기본 구현이 제공됩니다 . 우리는 처음 두 가지 구현에 초점을 맞출 것입니다.
3.1. SavedRequestAwareAuthenticationSuccessHandler
SavedRequestAwareAuthenticationSuccessHandler 는 세션에 저장된 저장된 요청을 사용합니다. 로그인에 성공하면 사용자는 원래 요청에 저장된 URL로 리디렉션됩니다.
양식 로그인의 경우 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();
}
}
동등한 XML은 다음과 같습니다.
<http>
<intercept-url pattern="/login" access="permitAll"/>
<intercept-url pattern="/**" access="isAuthenticated()"/>
<form-login />
</http>
"/secured" 위치에 Security 리소스가 있다고 가정합니다. 리소스에 처음 액세스하는 경우 로그인 페이지로 리디렉션됩니다. 자격 증명을 입력하고 로그인 양식을 게시하면 원래 요청한 리소스 위치로 다시 리디렉션됩니다.
@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 는 리디렉션 결정에 대한 더 많은 옵션을 제공합니다.
setUserReferer(true) 로 참조 자 기반 리디렉션을 활성화할 수 있습니다 .
public class RefererRedirectionAuthenticationSuccessHandler
extends SimpleUrlAuthenticationSuccessHandler
implements AuthenticationSuccessHandler {
public RefererRedirectionAuthenticationSuccessHandler() {
super();
setUseReferer(true);
}
}
그런 다음 RedirectionSecurityConfig 에서 AuthenticationSuccessHandler 로 사용합니다 .
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/login*")
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.successHandler(new RefererAuthenticationSuccessHandler());
return http.build();
}
그리고 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 의 사용하기 쉬운 기능에는 마법이 없습니다 . 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 에서 찾을 수 있습니다 .