로그인 성공 시 이전 페이지로 이동 - Referer 헤더와 AuthenticationSuccessHandler extends
Spring

로그인 성공 시 이전 페이지로 이동 - Referer 헤더와 AuthenticationSuccessHandler extends

발생한 문제 : 게시판 웹사이트에서 댓글 작성 페이지 -> 로그인 페이지로 redirect 된 후, 로그인 성공 시 댓글 작성 페이지로 되돌아오는 것이 아닌 index page ("/")로 돌아오게 된다.

 

해결 : 

1. 스프링 시큐리티는 권한이 없는 페이지에 대해서 login form 페이지로 redirect 된다.

2. 이 때 이전 페이지에 대한 url을 Referer 헤더로 request에 가지고 있다.

3. Referer 헤더값(이전 페이지에 대한 url)을 Session에 저장한다.

4. 로그인 성공 시 동작하는 AuthenticationSuccessHandler를 상속받아 구현한다.

5. 이 때 Referer 헤더값을 Session에서 꺼내서 해당 url로 redirect한다.



위 순서대로 문제를 해결하였다.

아래는 3~5번에 대한 소스코드이다.

 

3. Referer 헤더값(이전 페이지에 대한 url)을 Session에 저장한다.

// UserController.java

@GetMapping("/login")
public String loginForm(
        HttpServletRequest request,
        Model model) {

    ....
    
    /**
     * 이전 페이지로 되돌아가기 위한 Referer 헤더값을 세션의 prevPage attribute로 저장 
     */
    String uri = request.getHeader("Referer");
    if (uri != null && !uri.contains("/login")) {
        request.getSession().setAttribute("prevPage", uri);
    }
    return "auth/loginForm";
}

loginForm에 진입 할 때, request.getHeader("Referer")를 통해 이전 페이지에 대한 uri 받아온다.

이 때 해당 uri가 null이라면 이전 페이지가 존재하지 않는다는 것이고, uri가 "/login"을 포함한다면 로그인 실패 등의 이유로 이전 페이지가 loginForm에 대한 uri인 것이므로 session에 해당 uri를 저장하지 않는다. (로그인 성공 후 다시 loginForm으로 이동할 필요는 없다)

uri가 null이 아니고 "/login"을 포함하지 않는다면 session에 해당 uri의 값을 저장한다.

 

 

4. 로그인 성공 시 동작하는 AuthenticationSuccessHandler를 상속받아 구현한다.

5. 이 때 Referer 헤더값을 Session에서 꺼내서 해당 url로 redirect한다.

 

// CustomAuthSuccessHandler.java
@Component
public class CustomAuthSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

    private final RequestCache requestCache = new HttpSessionRequestCache();
    private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException {

        clearSession(request);

        SavedRequest savedRequest = requestCache.getRequest(request, response);

        /**
         * prevPage가 존재하는 경우 = 사용자가 직접 /auth/login 경로로 로그인 요청
         * 기존 Session의 prevPage attribute 제거
         */
        String prevPage = (String) request.getSession().getAttribute("prevPage");
        if (prevPage != null) {
            request.getSession().removeAttribute("prevPage");
        }

        // 기본 URI
        String uri = "/";

        /**
         * savedRequest 존재하는 경우 = 인증 권한이 없는 페이지 접근
         * Security Filter가 인터셉트하여 savedRequest에 세션 저장
         */
        if (savedRequest != null) {
            uri = savedRequest.getRedirectUrl();
        } else if (prevPage != null && !prevPage.equals("")) {
            // 회원가입 - 로그인으로 넘어온 경우 "/"로 redirect
            if (prevPage.contains("/auth/join")) {
                uri = "/";
            } else {
                uri = prevPage;
            }
        }

        redirectStrategy.sendRedirect(request, response, uri);
    }

    // 로그인 실패 후 성공 시 남아있는 에러 세션 제거
    protected void clearSession(HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        if (session != null) {
            session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
        }
    }
}

세션에 저장한 이전 페이지 uri를 사용하여 로그인 성공 시 해당 uri로 redirect 할 수 있게 해보자.

SimpleUrlAuthenticationSuccessHandler를 상속받아 구현한다.

 

AuthenticationSuccessHandler는 이름 그대로 인증에 성공 후 동작하는 핸들러로,

로그인 성공 시 onAuthenticationSuccess 메서드가 동작한다.

 

먼저, clearSession 메서드를 정의하여 로그인 성공 시 이전에 에러 세션(로그인 실패 기록)이 존재한다면 제거해주는 작업을 수행한다.

 

그 후 requestCache.getRequest를 통해 SavedRequest를 꺼내올 수 있는데,

SavedRequest는 우리가 위에서 세션에 이전 페이지의 uri를 세션에 저장하는 작업을 Spring Security가 수행한 것이라고 생각할 수 있다.

 

SavedRequest가 존재한다는 것은 *권한이 없는 페이지에 접근하여 Spring Security가 해당 작업을 Intercept해 이전 페이지에 대한 uri를 SavedRequest에 저장하고, login page로 redirect했음을 의미한다. 이 경우에는 Spring Security가 친절하게 SavedRequest에 uri를 저장해두었으므로 해당 uri를 사용하면 된다.

 

*ex) 사용자가 로그인 되어있지 않은 상태로 "글 작성" 버튼을 누르는 등 권한이 없는 페이지로 이동했을 때 

 

SavedRequest가 존재하지 않는다는 것은 *사용자가 직접 로그인 페이지에 접근하여 로그인을 수행한 경우로, Spring Security가 해당 작업을 Intercept 하지 않았음을 의미한다. 그러므로 SavedRequest의 값이 null이고, 이전 페이지에 대한 uri를 SavedRequest에 저장하지 않는다. 이 때 우리가 세션에 저장한 uri를 사용하면 된다.

 

*ex) 사용자가 "로그인" 버튼을 눌러 직접 로그인 페이지로 이동했을 때

 

savedRequest의 존재여부에 따라 redirect 할 uri를 결정하게 되는데, savedRequest가 null이 아니라면savedRequest.getRedirectUrl()을 통해 Spring Security가 저장해놓은 이전 페이지의 uri를 사용한다.

savedRequest가 null이라면 세션의 uri 값에 따라 다시 한번 조건을 분기한다.

 1. uri의 값이 존재하지 않는다면 -> uri = "/"

 2. uri의 값이 존재하지만, "auth/join"을 포함하고 있다면 -> uri = "/" (회원가입 페이지 -> 로그인 페이지 순서로 이동한 것이므로 로그인 성공 후 회원가입 페이지로 다시 이동하게 하지 않고 index page로 이동)

 3. uri의 값이 존재하고, "auth/join"을 포함하지 않는다면 -> uri = 세션에 저장한 이전 페이지 uri

 

redirectStrategy를 통해 로그인 성공 후 결정된 uri로 redirect한다.

 

** 해당 AuthenticationSuccessHandler를 꼭 WebSecurityConfigurerAdapter의 configure에서 추가해주어야 한다!

// SecurityConfig.java

@Configuration
@RequiredArgsConstructor
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final AuthenticationSuccessHandler authenticationSuccessHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/api/posts/**").hasRole(Role.USER.name()) // 등록, 수정, 삭제
                .antMatchers("/posts/add/**").hasRole(Role.USER.name()) // 등록 Form
                .antMatchers("/posts/{\\d+}/edit").hasRole(Role.USER.name()) // 수정 Form
                .anyRequest().permitAll()
                .and()
                .formLogin()
                .loginPage("/auth/login")
                .loginProcessingUrl("/auth/loginProc")
                .successHandler(authenticationSuccessHandler) // 꼭 추가해주세요!
                .and()
                .logout()
                .logoutRequestMatcher(new AntPathRequestMatcher("/auth/logout"))
                .invalidateHttpSession(true).deleteCookies("JSESSIONID")
                .logoutSuccessUrl("/");
    }
}

 

 

해결 후 동작 : 

글 상세 페이지에서 직접 로그인 버튼을 통해 로그인 페이지로 접근하여 Spring Security가 인터셉트 할 수 없는 작업이었지만, AuthenticationSuccessHandler를 통해 로그인 성공 후 다시 이전 페이지인 글 상세 페이지로 redirect 되었다.

 

 

소스코드 : https://github.com/HunSeongPark/lolcruit

 

GitHub - HunSeongPark/lolcruit: 롤크루트 - 리그오브레전드 라이너 채용공고 게시판 🎲

롤크루트 - 리그오브레전드 라이너 채용공고 게시판 🎲. Contribute to HunSeongPark/lolcruit development by creating an account on GitHub.

github.com

 

참고 : https://codevang.tistory.com/269

 

스프링 Security_로그인_로그인 성공 대응 로직 [4/9]

- Develop OS : Windows10 Ent, 64bit - WEB/WAS Server : Tomcat v9.0 - DBMS : MySQL 5.7.29 for Linux (Docker) - Language : JAVA 1.8 (JDK 1.8) - Framwork : Spring 3.1.1 Release - Build Tool : Maven 3.6..

codevang.tistory.com