Spring Security : Login 필터 + API의 이해

    이 게시글은 인프런 정수원님의 강의를 복습하며 작성한 글입니다.

    Spring Security Login 기능

    Spring Security는 Login 기능을 제공해준다. Spring MVC의 필터나 인터셉터 기능을 이용해서 로그인 기능을 구현할 수도 있으나, Spring Security가 제공하는 Login 기능을 이용하면 더욱 더 쉽게 처리를 할 수 있게 된다. 

    간단히 정리하고 들어가보면 Spring Security Login 기능은 필터를 기준으로 처리가 되고, 인증이 완료되면 이를 바탕으로 인증 객체가 만들어진다. 그리고 만들어진 인증 객체는 Security Context에 JSESSION ID와 함께 저장되고, SESSION에 Security Context는 저장된다. 

    그리고 사용자는 JSESSIONID를 받게 되는데, 추후에 WAS에 요청을 할 때 사용자는 JSSESIONID를 요청 헤더에 포함하는 것만으로 로그인 인증이 되는 방식으로 동작한다. 

     

    Spring Security Login API

    http
            .formLogin()
            .loginProcessingUrl("/login")
            .successHandler(sucessHandler())
            .failureHandler(failureHandler())
            .passwordParameter("password")
            .usernameParameter("username")
            .loginPage("/login.html");
            .failureUrl("/login.html?error=true")
            .defaultSuccessUrl("/home");

    Spring Security는 WebSecurityConfiguereAdapter 클래스의 Configuer 메서드를 통해서 설정할 수 있다. Http 객체에 이런저런 API를 추가해서 설정할 수 있다. 

    • formLogin : 로그인 폼 방식으로 인증
    • loginProcessingUrl : 로그인 페이지에서 로그인 폼의 Action을 가리킴. Action은 주로 Form을 어떤 URL로 제출할지를 의미함.
    • successHandler : 인증 성공한 다음에 해야할 일을 설정한다. 
    • failureHandler : 인증 실패한 다음에 해야할 일을 설정한다. 
    • passwordParameter : 로그인 페이지의 password의 ID 값을 설정
    • usernameParameter : 로그인 페이지의 username의 ID 값을 설정
    • loginPage : 보여줄 로그인 URL 설정 (컨트롤러 개념)
    • failureUrl : 인증 실패할 경우 URL 설정 (컨트롤러 개념)
    • defaultSuccessurl : 인증 성공할 경우, 자동으로 이동하는 URL 설정(컨트롤러 개념) 

     

     

    Spring Security Login API 삽질 주의

    http
                    .formLogin()
                    .successHandler(new AuthenticationSuccessHandler() {
                        @Override
                        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                            System.out.println("성공 : " + authentication.getName());
                            response.sendRedirect("/");
                        }
                    })
                    .failureHandler(new AuthenticationFailureHandler() {
                        @Override
                        public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
                            System.out.println("로그인 실패 : " + exception.getMessage());
                            response.sendRedirect("/login");
                        }
                    })
                    .usernameParameter("username")
                    .passwordParameter("password")
                    .permitAll()
            //                .loginPage("/login") //
            //                .failureUrl("/login")
    //                .defaultSuccessUrl("/") // 이게 있으면 동작하지 않음.
    //                .loginProcessingUrl("/login")

    1. 핸들러 API / URL API 동시 설정 금지

    succ Handler / fail Handler는 각각 인증이 성공, 실패 했을 때의 동작을 좀 더 상세히 동작하도록 한다. 예를 들어 로그를 출력하고 리다이렉트를 하거나 하는 것을 구현할 수 있다. 그런데 코드를 테스트 하다보니 문제가 확인되었다. 

    fail URL / default URL을 설정하게 되면 Handler가 먹히지 않는다는 것이엇다. 그래서 FAIL / Default URL 설정을 주석처리하고 동작을 확인해보니 각각 핸들러에 설정한 로깅 + Redirect가 정상 동작하는 것을 확인했다. 

     

    2. login API + permitAll() API

    로그인 API를 설정할 경우 permitAll() API를 사용해줘야한다. 기본적으로 아무 설정을 하지 않았다면, 이 웹으로 접근되는 모든 요청은 차단된다. 이것은 별도 설정된 로그인 API도 적용된다. 따라서 로그인 페이지에 아무리 접근하려고 해도, 권한이 없어서 접근이 안되고, 계속 Failure URL로 접근하게 된다. 그래서 너무 많은 리다이렉트로 문제가 발생한다. 

    이를 방지하기 위해서 permitAll() API가 제공된다. 이 API를 사용하게 되면, 로그인 페이지로는 인증 없이 자유롭게 접근을 할 수 있게 된다. 

     

     

     

    Spring Security 로그인 폼 인증 방식

    1. AbstractAuthneticationProcessFilter가 usernamePasswordAuthenticationFilter를 호출한다.
    2. usernamePasswordAuthenticationFilter는 AndPathRequestMatcher를 이용해 "/login"처럼 login URL로 접근한 것인지 확인한다. login URL로 접근한 것이 아니라면, 이 필터에 걸리는 것이 아니기 때문에 다음 필터로 넘겨준다.
    3. Authentication 인증 객체를 만들고, Request에 전달된 username / password 정보를 넣어준다.
    4. 필터는 AuthenticationManager에게 인증 객체를 전달한다.
    5. Auth Manager는 내부적으로 Auth Provider를 가지고 있고, 실제 인증 처리를 Auth Provider를 통해 처리한다. 인증을 성공하면 Auth Provider는 인증 객체를 만들어서 다시 Return 해주고, 인증에 실패하면 Exception을 던진다. 
    6. 인증 객체를 다시 필터로 전달해준다. 필터는 Security Context를 만들고, 전달받은 인증 객체를 Security Context에 저장해준다. 이 때, Session ID도 같이 저장된다. 
    7. 인증이 성공한 후에 SucessHandler로 넘어가게 된다.

     

     

    Spring Security 사용자 정의 보안 기능 코드 추적

    AbstractAuthenticationProcessingFilter

    인증 요청이 오면 AbstractAuthenticationProcessFilter 클래스로 이동해서, doFilter() 메서드가 호출된다. 이 클래스는 private로 doFilter() 메서드를 또 가지고 있는데, Request / Response / chian 객체를 전달해주면서 doFilter()를 호출해준다. 

    AbstractAuthenticationProcessingFilter.doFilter()

    들어오면 먼저 이 요청이 인증이 필요한 요청인지를 requiresAuthentication 메서드를 이용해서 확인한다. 만약에 인증이 필요없는 요청이라고 한다면 다음 Filter로 체이닝을 해준다. 

    만약 인증이 필요한 객체라면 attemptAuthentication 메서드를 통해서 인증을 실행한다.

    UsernamePasswordAuthenticationFilter.attemptAuthentication()

    attemptAuthentication() 메서드를 통해 인증을 시도한다. 이 메서드에서는 username / password 정보를 가진 인증 객체를 하나 만든다. 그리고 this.getAuthenticationManger().authenticate 메서드를 통해서 인증 객체를 AuthenticationManager에게 전달해준다. 

    AuthenticationManager

    AutheniticationManager는 내부적으로 여러 Provider를 가지고 있다. 매니저는 이 모든 Provider를 for문으로 순회하면서 인증이 가능한지 확인한다. 

    AuthenticationManager

    다음과 같이 인증이 가능한지 확인하고, 인증이 가능하면 그 결과를 result에 저장해서 return해준다. 그렇지 않으면 Exception을 만들어 던져준다. 매니저는 그 값을 return 받고 다시 한번 UsernamePasswordAuthenticationFilter로 인증 객체를 돌려준다.

    UsernamePasswordAuthenticationFilter

    Filter는 인증 객체를 전달받지 못한 경우, 인증이 실패한 것이기 때문에 다음 필터로 넘어가지 않고 return을 한다. 그러면 로그인이 되지 않게 된다. 

    UsernamePasswordAuthenticationFilter

    인증이 성공한 경우 succesfulAuthentication에 인증 객체를 전달해준다.

    UsernamePasswordAuthenticationFilter

    이 메서드에서는 먼저 Security Context를 하나 만든다. 그리고 Security Context에 전달받은 인증 객체를 넣어주고 돌아간다.

    Security Context는 다음과 같이 IP 주소와 SessionID까지 같이 저장되는 것이 보인다. 로그인이 완료되면 JSSESIONID를 돌려주는데, 나중에 Security Context를 이 Session ID를 기반으로 찾는다. 

    UsernamePasswordAuthenticationFilter

    Security Context에 저장이 완료되었으며, 이후 필터는 successHandler를 호출해서, SucceHandler에 설정되어있는 로그인 성공 후 해야할 일을 처리해준다. 

    위 흐름을 보기 쉽게 정리하면 이렇다.

     

    정리

    • Spring Security의 로그인은 필터를 통해서 처리가 되고, 필터는 Auth Manager에게, Auth Manager는 Auth Provider에게 요청해서 인증을 처리한다.
    • 인증이 완료되면 인증 객체가 만들어지고, Security Context에 저장된다. Security Context는 내부적으로 JSSESION ID도 함께 가진다.
    • 만들어진 Security Context는 Spring WAS의 Session에 저장된다. 

    댓글

    Designed by JB FACTORY