Spring Security

SpringSecurity - UsernamePasswordAuthenticationFilter

jaewoo 2023. 2. 15. 23:05

 

 

Spring Security에서 FormLogin 방식으로 로그인 할 경우

먼저 SecurityFilterChain 설정 메소드에서 각종 설정 변경하기

    http
                .formLogin()
                .loginPage("/login")
                .defaultSuccessUrl("/")	//로그인성공
                .failureUrl("/login")	//로그인실패
                .usernameParameter("email")	//html name 설정
                .passwordParameter("password")	//html name 설정
                .loginProcessingUrl("/login/process") //로그인 form action url	
                .successHandler(new AuthenticationSuccessHandler() {
                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                        System.out.println("성공!");
                        response.sendRedirect("/");
                    }
                })
                .failureHandler(new AuthenticationFailureHandler() {
                    @Override
                    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
                        System.out.println("실패");
                        response.sendRedirect("/login");
                    }
                })
                .permitAll();

 

 

이러한 설정을 안 한다면 loginProcessingUrl은 /login이다. 만약 username, password로 post 요청을 날릴 경우 해당 요청을 UsernamePasswordAuthenticationFIlter가 요청을 낚아채고 username, password를 검증한다. 이 과정에서 해당 Filter에 있는 attemptAuthentication 메소드가 호출된다.

해당 메소드는 HttpServletRequest(요청객체)에서 username, password를 뽑아서 Token(Authenticaiton객체)를 만든다.

POST 외에는 동작하지 않도록 처리되어있다. 이렇게 만든 토큰을 ProviderManager(Authentication 구현체)에게 인증 책임을 위임한다.

ProviderManager.authenticate ( 너무 김)

ProviderManager는 자신이 가진 provider들을 순회하면서 인증을 처리할 수있는 Provider를 찾아 인증 책임을 위임한다.

 

AbstractUserDetailsAuthenticationProvider 당첨

userCache에서 UserDetails객체를 가져오려 시도하는데 여기서  false일 경우 retrieveUser 메소드를 실행하여 가져오려 시도한다. 해당 메소드는 

DaoAuthenticationProvider가 가지고 있는 메소드이다. 여기서 잘 보면

UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);

이렇게 해당 객체를 가져온다. 여기서 UserDetailsService는 우리가 빈으로 등록해야하는 UserDetailsService 이다.(구현해야함)

 

이어서 AbstractUserDetailsAuthenticationProvider를 보면 retrieveUser 메소드에서 Exception을 일으키지 않았을 경우 

additionalAuthenticationChecks 메소드를 실행한다. 여기서 넘겨주는 값은 user는 위에서 리턴된 UserDetails 객체이고 옆에 토큰은 우리의 입력(username, password)를 통해 만든 토큰이다. 

 

해당 메소드 역시 DaoAuthenticationProvider가 구현한다.

설명하자면 우리의 입력을 통해 만들어진 토큰과 해당 토큰에서 username으로 불러온 UserDetails 객체와 비밀번호를 비교한다. 만약 틀리면 BadCredentialsException을 일으킨다.

 

다시 이어서 AbstractUserDetailsAuthenticationProvider 에서

principalToReturn은 UserDetails이다

그렇다 다시 제대로 된 UsernamePasswordAuthenticationToken 을 생성한다. 이번 토큰에는 Authorities( 권한)이 들어간다. 그렇기 때문에 UserDetails를 구현할때 권한을 넣어줘야한다. 

 

 

이렇게 ProviderManager한테 다시 간다. (Authentication - 인증 객체를 가지고)

 

마지막은 다시 UsernamePasswordAuthenticationFilter로 

돌아왔다가 

AbstractAuthenticationProcessingFilter

해당 필터로 간다.

AbstractAuthenticationProcessingFilter.doFilter

뒤에도 디버깅으로 계속해봤는데 정말 상상이상을 필터가 많다.... 

 

정리

- Form 기반 로그인을 할 경우 사용되는 필터 (UsernamePasswordAuthenticationFIlter)

- 흐름은 해당 필터가 ProviderManager를 호출해서 인증을 시도한다. -> 중간에 디버깅에서 빠졌지만 AbstractUserDetailsAuthenticationProvider로 가기전에 여기서  선택됨

- ProviderManager는 이때 인증을 위해서 AbstractUserDetailsAuthenticationProvider를 호출하고 이때 계정에 상태나 패스워드 일치 여부 등을 파악하는데 그러기 위해서는 유저 정보가 필요하여 DaoAuthenticationProvider를 통해서 유저 정보를 받는다.

- 여기서 DaoAuthenticationProvider는 UserDetailsService 우리가 구현해 빈으로 등록한 놈에게서 정보를 받아온다.

 

 

 

참고

https://whitepro.tistory.com/473

 

[스프링 시큐리티] 2. Form Login 인증 방식 파악 UsernamePasswordAuthenticationFilter, AnthPathRequestMatcher

1. Form Login formLogin의 API 살펴보기 http.formLogin() 으로 작동시킬 수 있다. 그리고 chaining 방식으로 아래와 같이 상황에 따른 페이지 설정을 해줄 수 있다. http.formLogin() .loginPage(“/login.html") // 사용자

whitepro.tistory.com