[ 정수원 스프링 시큐리티 #1 ] 스프링 시큐리티 기본 API & Filter 이해 (2)

김수호·2024년 3월 3일
0
post-thumbnail

지난 포스팅에 이어, 이번 포스팅에서는 3) ~ 5) 까지의 내용을 정리한다.

👉 목차는 다음과 같다.

1) 인증 API - 프로젝트 구성 및 의존성 추가
2) 인증 API - 사용자 정의 보안 기능 구현
3) 인증 API - Form Login 인증
4) 인증 API - Form Login 인증 필터 : UsernamePasswordAuthenticationFilter
5) 인증 API - Logout 처리, LogoutFilter

6) 인증 API - Remember Me 인증
7) 인증 API - Remember Me 인증 필터 : RememberMeAuthenticationFilter
8) 인증 API - 익명사용자 인증 필터 : AnonymousAuthenticationFilter
9) 인증 API - 동시 세션 제어, 세션 고정 보호, 세션 정책
10) 인증 API - 세션 제어 필터 : SessionManagementFilter, ConcurrentSessionFilter
11) 인가 API - 권한 설정 및 표현식
12) 인증/인가 API - 예외 처리 및 요청 캐시 필터 : ExceptionTranslationFilter, RequestCacheAwareFilter
13) Form 인증 - 사이트 간 요청 위조 : CSRF, CsrfFilter

바로 하나씩 확인해보자.


3) Form Login 인증

클라이언트와 서버간의 관계속에서 스프링 시큐리티가 인증 처리를 하는 프로세스에 대해서 간단하게 살펴보자.

  • 참고)
    • 먼저 사용자가 GET 방식으로 /home url로 자원에 접근을 시도한다.
    • 현재 서버 보안 정책에는 모든 자원에 접근 시 인증된 사용자만 접근 가능하도록 설정되어 있다. 따라서 현재 사용자가 인증을 받지 않았다면, 로그인 페이지로 이동시킨다.
      • 참고로 로그인 페이지는 우리가 별도로 만들 수도 있고, 만들지 않았다면 스프링 시큐리티가 기본적으로 제공해주는 페이지가 띄워진다.
    • 사용자가 username과 password를 입력 후 POST 방식으로 다시 인증을 시도한다.
    • 서버에서는 SESSION 및 인증 토큰을 생성하고 저장한다.
    • (인증 이후) 다시 클라이언트가 GET 방식으로 /home url로 자원에 접근을 시도한다.
    • 스프링 시큐리티는 현재 사용자가 가진 세션에 저장된 인증 토큰의 존재여부를 확인하여 유효한지 여부를 판단한다.

 

스프링 시큐리티가 제공하는 Form Login 인증과 관련된 API에 대해서 살펴보자.

  • 참고)
    • http.formLogin() 을 작성함으로써 Form 로그인 인증 기능이 작동한다.
    • 하위 API 에는 loginPage(..), defaultSuccessUrl(..) 등이 있다. ( 자세한 설명은 위 그림 참고 )

 

👉 위 API 들을 사용해서 직접 코드를 작성해보자.

  • SecurityConfig 수정
    • formLogin() : Form 로그인 인증 방식이 작동한다.
    • loginPage(..) : 사용자 정의 로그인 화면 URL 경로를 작성한다. (default: /login)
    • defaultSuccessUrl(..) : 인증에 성공 시 이동할 URL 경로를 작성한다. (default: /)
    • failureUrl(..) : 인증에 실패 시 이동할 URL 경로를 작성한다.
    • usernameParameter(..) : 폼 태그의 아이디 태그 name (default: username)
    • passwordParameter(..) : 폼 태그의 패스워드 태그 name (default: password)
    • loginProcessingUrl(..) : 로그인 처리 경로를 작성한다. 로그인 요청 POST url (default: /login)
    • successHandler(handler) : 인증 성공 후 호출할 핸들러 클래스를 작성한다.
      • AuthenticationSuccessHandler 인터페이스를 구현한 구현체 클래스를 작성한다.
    • failureHandler(handler) : 인증 실패 후 호출할 핸들러 클래스를 작성한다.
      • AuthenticationFailureHandler 인터페이스를 구현한 구현체 클래스를 작성한다.
    • permitAll() : 사용자 정의 로그인 페이지 접근 권한 승인.
      • 현재 위 인가 정책에 의해 모든 경로 접근시 인증이 필요한데, /loginPage는 인증 받지 않아도 누구나 접근 가능해야 한다.
    • (참고) 위와 같이 사용자 정의 로그인 페이지를 직접 제공할 때는 로그인 html 화면의 폼 태그 정보와 usernameParameter, passwordParameter, loginProcessingUrl 정보를 맞춰줘야 한다.
    • (참고) defaultSuccessUrl 또는 failureUrl 보다 successHandlerfailureHandler 가 우선순위를 갖는다.
    • (참고) https://www.inflearn.com/questions/193737/defaultsuccessurl-%EC%9E%91%EB%8F%99-%EC%88%9C%EC%84%9C
  • SecurityController 추가
    • 참고로 이번 내용에서는 /loginPage 경로에 해당하는 화면은 만들지 않았고, /loginPage 경로 요청시 화면에 "loginPage" 문자열이 잘 나오는지만 확인한다.
  • 테스트 해보면 정상적으로 작성한 대로 동작하는 것을 확인할 수 있다.
    • 루트 경로 접근시 /loginPage 로 이동한다.
    • SecurityConfig 에서 loginPage(..)를 주석 처리 후, successHandler와 failureHandler를 확인한다.
    • successHandler와 failureHandler를 주석 처리 후, 나머지 기능을 확인한다.

 

이번 내용에서는 Form Login 인증 방식에 해당 하는 여러가지 API 들을 작성해서 기능을 확인하고 테스트해보았다.

다음 내용에서는 Form Login 인증 처리를 하는 필터인 UsernamePasswordAuthenticationFilter 에 대해서 알아보자.


4) Form Login 인증 필터 : UsernamePasswordAuthenticationFilter

이번 내용에서는 Form 로그인 인증 처리를 하는 필터인 UsernamePasswordAuthenticationFilter 에 대해서 살펴보자.

먼저 이전 내용에서 우리는 Form Login API를 설정해 보았다. 그리고 그와 관련된 하위 API들도 설정했다. 그렇게 설정하게 되면 초기화 되면서 로그인 인증 처리가 작동할 수 있도록 스프링 시큐리티가 구성을 하게 된다. 그리고 이후 사용자가 로그인 시도를 하게 되면 인증 처리가 이뤄지는데, 그 인증 처리를 담당하고, 인증 처리와 관련된 요청을 처리하는 필터가 바로 UsernamePasswordAuthenticationFilter 이다.

UsernamePasswordAuthenticationFilter 가 내부적으로 어떻게 인증 처리를 하는지 흐름을 살펴보고, 그 과정에서 어떤 클래스가 어떤 역할을 하는지도 알아보자.

  • 참고)
    • 1) 사용자가 인증을 시도한다.
    • 2) UsernamePasswordAuthenticationFilter 가 그 요청을 받는다.
    • 3) UsernamePasswordAuthenticationFilter 는 먼저, AntPathRequestMatcher(/login)를 통해서, 현재 사용자가 요청한 url 요청 정보가 /login으로 시작되는지 확인 및 검사한다.
      • 참고로 AntPathRequestMatcher 디폴트는 /login 이며, 변경 가능하다.
        • loginProcessingUrl(..) 에서 설정한 값과 매핑된다. 따라서 loginProcessingUrl(..) 의 값을 직접 설정하게 되면 그 변경한 값으로 적용된다.
    • 4) url 정보가 매칭이 되면 실제 인증 처리를 진행하고, 매칭되지 않으면 인증 처리를 하지 않고, 그 다음 필터로 이동한다.
    • 5) (url 정보가 매칭이 되면) 이후 UsernamePasswordAuthenticationFilter 는 Authentication 객체를 생성하고, 그 객체에 사용자가 로그인 시 입력한 username 과 password 정보를 저장해서, AuthenticationManager(인증 관리자)에게 실질적으로 인증을 요청한다.
    • 6) UsernamePasswordAuthenticationFilter 로 부터 인증 객체를 전달받은 AuthenticationManager(인증 관리자)는 내부적으로 AuthenticationProvider 타입의 객체들을 가지고 있는데, 이 객체들 중 하나를 선택해서 인증 처리를 위임한다.
    • 7) 실질적으로 인증처리를 담당하는 AuthenticationProvider 클래스는 인증 성공/실패 에 대한 결과를 리턴한다.
    • 8) (인증에 실패하게 되면) AuthenticationProvider는 AuthenticationException(인증 예외)를 발생시킨다. 그리고 해당 예외는 다시금 UsernamePasswordAuthenticationFilter 필터에게 전달되고, 필터는 failureHandler 와 같은 클래스에서 인증 실패 후속 작업을 처리하게 된다.
    • 9) (인증에 성공하게 되면) AuthenticationProvider 클래스는 Authentication 인증 객체를 생성해서, 인증에 성공한 결과 정보(회원 정보 객체, 권한 정보 등)를 저장한다. 그리고 AuthenticationManager 에게 다시 리턴한다.
    • 10) AuthenticationManager는 AuthenticationProvider에게 받은 최종 인증 객체를 다시 UsernamePasswordAuthenticationFilter 필터에게 리턴한다.
      • 즉 필터는 인증에 성공한 경우, Authentication 객체를 전달받는다. 이때 Authentication 객체 안에는 최종적으로 성공한 인증 결과 User 정보 + Authorities 정보 등이 있다.
    • 11) 필터는 인증 객체를 SecurityContext 객체에 저장한다. ( ex) SecurityContextHolder.getContext().setAuthentivation(authResult); )
      • SecurityContext 는 인증 객체를 저장하는 저장소이다. 이후에 자세히 학습한다. 참고로 이후에는 SecurityContext 객체가 세션에도 저장된다.
    • 12) 인증에 성공 이후에는 SuccessHandler를 통해 성공 이후의 작업들이 처리되게 된다.
    • (정리) UsernamePasswordAuthenticationFilter 는 Form Login 인증 처리를 하는 필터이다. 이 필터의 작업 내용은 (AuthenticationManager를 중심으로) 인증 처리를 하기 전 작업과 인증 처리가 끝나고 난 이후 작업으로 나뉠 수 있다. 인증 처리를 하기 전에는, 사용자 인증 요청 정보를 인증 객체에 담아서 AuthenticationManager에 전달해서 인증 처리를 맡긴다. 인증 처리가 끝난 이후에는 AuthenticationManager로 부터 최종 성공한 결과가 담긴 인증 객체를 전달받아서, 그 인증 객체를 SecurityContext 에 저장한다. (SecurityContext 는 전역적으로 인증 객체를 참조할 수 있도록 설계된 객체라고 생각하자.) 저장 후, SuccessHandler 와 같은 클래스를 통해서 인증에 성공한 이후의 후속작업을 진행한다.

5) Logout 처리, LogoutFilter

이번 내용에서는 Logout과 LogoutFilter에 대해서 알아보자.

  • 먼저 Logout 처리 과정을 간단히 살펴보자.
    • 클라이언트가 로그아웃을 요청한다.
    • 스프링 시큐리티가 그 요청을 받고 로그아웃 처리를 진행한다. 로그아웃 처리 과정에서는 다음과 같은 작업을 진행한다.
      • 1) 세션을 무효화 시킨다.
      • 2) 사용자 인증시 생성되었던 인증 토큰을 삭제하고, 인증 토큰이 저장되어 있는 SecurityContext 객체도 삭제한다.
      • 3) 삭제 해야할 쿠키가 있다면 쿠키 정보를 삭제한다.
      • 4) 로그아웃 성공시 로그인 페이지로 이동하도록 한다.
  • 로그아웃 기능을 사용하기 위해 설정할 수 있는 API는 다음과 같다.
    • http.logout() 과 같이 작성하면, 로그아웃 기능이 작동한다. 하위 API에는 logoutUrl(..), logoutSuccessUrl(..) 등이 있다.
    • 하위 API의 자세한 내용은 위 그림을 참고하자.
    • (참고) 스프링 시큐리티는 기본적으로는 로그아웃 처리시 원칙적으로 POST 방식으로 처리한다. (GET 방식으로 할 수도 있는데 그 부분은 뒤에서 학습한다.)

 

👉 코드로 적용해보자.

  • SecurityConfig 수정
    • (참고) 로그아웃 처리시 스프링 시큐리티가 기본적으로 제공하는 로그아웃 핸들러 구현체들이 있다. 그 구현체에서 세션을 무효화 시키고, 인증 토큰을 삭제하는 등의 처리를 하고 있는데, 그 처리 외에 우리가 별도로 어떤 처리를 하고자 하는 경우, LogoutHandler 인터페이스를 구현한 구현체 클래스를 작성해서 addLogoutHandler 에 설정하면 된다. ( 이때 핸들러가 적용되는 순서는 개발자가 직접 정의한 핸들러가 먼저 호출되고 그 다음 스프링 시큐리티에서 제공하는 핸들러가 적용된다. )
  • 실행해보면, 작성한 정책대로 동작하는 것을 확인할 수 있다.

 

지금까지 로그아웃 API를 통해서 다양한 로그아웃 관련된 설정들과 구현을 해보았다.
이번엔 LogoutFilter 에 대해서 살펴보자.

  • 참고)
    • 1) 사용자가 로그아웃을 요청한다.
    • 2) LogoutFilter 가 해당 요청을 받아 로그아웃 처리를 진행한다.
    • 3) LogoutFilter 는 AntPathRequestMatcher(/logout)를 통해서, 현재 사용자가 요청한 URL 요청 정보가 /logout인지 확인 및 검사한다.
      • 참고로 AntPathRequestMatcher 디폴트는 /logout 이며, 변경 가능하다. -> logoutUrl(..) 에서 설정한 값과 매핑된다. 따라서 logoutUrl(..) 의 값을 직접 설정하게 되면 그 변경한 값으로 적용된다.
    • 4) (url 정보가 매칭되지 않으면) 로그아웃 처리를 하지 않고 그 다음 필터로 이동한다.
    • 5) (url 정보가 매칭이 되면) LogoutFilter 는 SecurityContext 로부터 현재 인증된 사용자의 인증 정보를 담고 있는 Authentication 인증 객체를 꺼내와서, 로그아웃 핸들러(SecurityContextLogoutHandler)에게 전달한다.
      • 참고) LogoutFilter 가 가지고 있는 로그아웃 핸들러가 몇 개가 있다. 그 중에 SecurityContextLogoutHandler 핸들러 클래스는 세션을 무효화하고, 인증 객체를 null로 초기화 하는 등의 작업을 진행한다.
    • 6) 로그아웃 핸들러는 세션을 무효화하고, 인증 객체를 null로 초기화 하고, SecurityContextHolder.clearContext()를 통해 SecurityContext 객체를 삭제하는 등의 처리를 진행한다.
    • 7) 로그아웃 핸들러가 성공적으로 종료가 되면, 필터는 SimpleUrlLogoutSuccessHandler 클래스를 호출해서, 로그인 페이지로 이동하도록 처리한다.
      • logoutSuccessHandler를 직접 정의한 경우 직접 정의된 핸들러가 호출된다.

강의를 듣고 정리한 글입니다. 코드와 그림 등의 출처는 정수원 강사님께 있습니다.

profile
현실에서 한 발자국

0개의 댓글