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

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

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

👉 목차는 다음과 같다.

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

바로 하나씩 확인해보자.


9) 동시 세션 제어, 세션 고정 보호, 세션 정책

✔️ 동시 세션 제어

  • 동시 세션 제어는 현재 동일한 계정으로 인증을 받을 때, 생성되는 세션의 허용 개수가 초과되었을 경우에 어떻게 세션을 계속적으로 초과하지 않고 세션을 유지하는 지에 대한 제어를 말한다.
  • 스프링 스큐리티에서는 두가지 전략으로 동시 세션 제어를 제공한다.
    • 참고) 동일 계정으로 생성되는 세션의 최대 허용 개수는 1개라고 가정하자.
    • 첫 번째 전략이전 사용자의 세션을 만료시키는 전략이다.
      • 예를 들어, 사용자1과 사용자2가 있고 두 사용자는 동일한 계정으로 로그인을 시도한다고 가정하자. 먼저, 사용자1이 로그인을 시도한다. 그리고 서버에서 인증에 성공하여 사용자1에 대한 세션이 생성되었다. 이후 사용자2가 동일 계정으로 로그인을 시도한다. 그리고 서버에서 인증이 성공하여 사용자2에 대한 세션이 생성되었다. 그러면 현재 서버에는 동일한 계정으로 2개의 세션이 생성되게 된다. 그런데, 서버에서 최대 세션 허용 개수를 1개만 설정해뒀기 때문에 보안 정책에 어긋난다. 이때 첫 번째 전략의 경우는, 사용자2에 대한 로그인 세션이 생성되었으면, 그때, 이전 사용자의 세션을 만료시키는 설정을 하게 된다. 그렇게 되면, 세션은 2개인 상태지만 사용자1의 세션은 만료되도록 설정한 상태가 된다. 따라서 이후에 사용자1이 어떤 자원에 다시금 접근하려고 할때, 서버에서는 사용자1의 세션이 만료로 설정된 상태이므로, 그 즉시 사용자1의 세션을 실제로 만료시킨다.
    • 두 번째 전략현재 사용자의 인증을 실패하게 하는 전략이다.
      • 예를 들어, 사용자1과 사용자2가 있고 두 사용자는 동일한 계정으로 로그인을 시도한다고 가정하자. 먼저, 사용자1이 로그인을 시도한다. 그리고 서버에서 인증에 성공하여 사용자1에 대한 세션이 생성되었다. 이제 사용자2가 동일 계정으로 로그인을 시도한다. 그런데 지금 서버에서는 이미 동일한 계정으로 세션이 생성되어 있기 때문에, 세션이 생성되지 못하도록 인증 예외를 발생시켜 로그인을 차단시킨다. 그렇게 되면 첫 번째 사용자의 세션만 서버에 계속적으로 존재하게 된다.

 

그러면 동시 세션 제어를 어떻게 API로 제공하는지 살펴보자.

  • 참고)
    • http.sessionManagement() 을 작성함으로써 세션 관리 기능이 작동한다. 세션 관리 기능은 동시 세션 제어 뿐만 아니라 여러가지 기능을 제공한다. 이번에는 동시 세션 제어에 관련된 API를 살펴보자.
    • 하위 API 에는 maximumSessions(..), maxSessionsPreventsLogin(..) 등이 있다. ( 자세한 설명은 위 그림 참고 )
    • (참고) maxSessionsPreventsLogin(boolean) : 세션이 가득찼을 때 세션관리 정책. (최대 세션 허용 개수가 초과되었을 경우 처리 전략)
      • true: 신규 세션 허용하지 않고 기존 세션 유지. (위 ② 두 번째 전략에 해당한다.)
      • false (default): 기존 세션 만료 후 신규 세션 요청자 허용. (위 ① 첫 번째 전략에 해당한다.)
    • (참고) invalidSessionUrl(..)expiredUrl(..) 을 둘 다 설정한 경우, invalidSessionUrl(..) 설정이 우선한다.

 

👉 동시 세션 제어의 두 가지 유형에 대해서 코드로 작성해서 확인해보자.

  • SecurityConfig 수정
    • 최대 허용 가능 세션 수는 1개로 설정하고, 초과시 전략은 두 번째 전략을 사용했다.
      • maxSessionsPreventsLogin(boolean) 를 true로 설정.
  • 실행해보자. (서로 다른 브라우저 2개를 띄우고, 하나의 브라우저에서는 인증을 해보자.)
    • 그러면 현재는 세션이 하나 생성된 상태이다.
  • 이제 동일한 계정으로 다른 브라우저에서도 인증을 시도해보자.
    • 그러면 두 번째 전략에 의해서, 위와 같이 인증 처리되지 않으며, 최대 허용 가능한 세션 수를 초과했다는 메시지가 노출되는 것을 확인할 수 있다.
  • 이번엔 maxSessionsPreventsLogin(boolean)false 로 변경해보자.
    • 첫 번째 전략으로 설정했다.
  • 실행해보자. (서로 다른 브라우저 2개를 띄우고, 하나의 브라우저에서는 인증을 해보자.)
    • 그러면 현재는 세션이 하나 생성된 상태이다.
  • 이제 동일한 계정으로 다른 브라우저에서도 인증을 시도해보자.
    • 다른 브라우저에서도 인증시 인증에 성공한다.
    • 대신 두번째 브라우저의 인증에 의해 세션이 생성된 그 순간에 첫번째 브라우저의 세션을 만료시키는 설정이 이뤄진다.
    • 따라서 지금 서버에는 동일한 계정으로 세션이 두개 생성된 상태이긴 하지만, 첫번째 사용자의 세션은 만료로 설정된 상태이다.
  • 첫 번째 브라우저를 새로고침 해보자.
    • 첫 번째 브라우저에서 자원에 접근하는 순간 사용자의 세션은 즉시 만료 처리되고, 위와 같이 세션이 만료되었다는 메시지가 노출된다.
    • 참고) 첫 번째 브라우저에서 자원에 접근하려고 할 때, 서버가 현재 사용자의 세션이 만료되었는지 안되었는지 체크한다. (ConcurrentSessionFilter)

지금까지 동시 세션 제어에 대해서 알아보았고, 동시 세션 제어에 대한 API를 테스트하고 확인해 보았다.


이번엔 세션 고정 보호에 대해서 알아보자.

✔️ 세션 고정 보호

  • 참고)
    • 먼저, 공격자가 서버에 접속한다. 그러면 서버는 공격자에게 JSESSIONID(123456) 쿠키를 발급한다.
    • 이 상태에서 공격자는 사용자에게 자신이 발급받은 세션쿠키를 심어둔다. (그러면 사용자는 현재 공격자가 심어둔 쿠키를 가지고 있는 상태가 된다.)
    • 이 상태에서 사용자는 공격자가 심어둔 세션쿠키로 로그인을 시도한다. 그리고 서버에서 인증에 성공하여 세션이 생성된다. ( 그러면 인증을 받을 때, JSESSIONID(123456) 쿠키로 접근을 하고, 이 쿠키에 해당하는 세션이 인증받은 상태이다. 따라서 지금 서버는 사용자나 공격자나 동일한 쿠키로 접근하게 되면, 두 사용자 모두가 해당 세션을 공유하게 된다. 그렇기 때문에 공격자는 인증을 하지 않고도 JSESSIONID(123456) 값만 가지고 접근하게 되면, 마치 사용자가 인증받은 것 처럼 공격자도 인증받은 사용자로 해당 사용자의 정보를 공유할 수 있게 된다. => "세션 고정 공격")

이 세션 고정 공격을 방지하기 위해서 스프링 시큐리티는 "세션 고정 보호" 라는 기능을 제공한다.

🤔 그러면 어떻게 방지할까?

  • 사용자가 비록 공격자가 심어둔 쿠키로 접속을 해서 인증을 시도한다 하더라도, 인증을 할 때 마다 새로운 세션아이디가 생성되도록 한다. 결과적으로 생성된 세션아이디로 쿠키도 생성되므로, 공격자가 심어놓은 JSESSIONID로는 세션아이디가 다르기 때문에, 더이상 사용자의 정보를 공유할 수 없다.

이렇게 인증에 성공할 때 마다 새로운 세션 아이디가 발급되도록 처리해주는 것이 세션 고정 보호 이다.

그러면 세션 고정 보호와 관련된 API가 어떻게 제공되는지 확인해보자.

  • 참고)
    • 동시 세션 제어와 같이 http.sessionManagement() 을 작성함으로써 세션 관리 기능이 작동한다.
    • 참고) sessionFixation().changeSessionId() : (서블릿 3.1 이상 기준) 세션 고정 보호 기본 값이다.
      • 사용자의 인증 시 마다 세션아이디를 새로 발급한다.
      • 이전 세션에서 설정한 여러 속성의 값들을 사용 가능하다.
    • 참고) sessionFixation().migrateSession() : (서블릿 3.1 이하 기준) 세션 고정 보호 기본 값이다.
      • 사용자의 인증 시 마다 새로운 세션을 생성하고, 세션아이디도 새로 발급한다.
      • 이전 세션에서 설정한 여러 속성의 값들을 사용 가능하다.
    • 참고) sessionFixation().newSession()
      • 사용자의 인증 시 마다 새로운 세션을 생성하고, 세션아이디도 새로 발급한다.
      • 단, 이전 세션에서 설정한 여러 속성의 값들을 사용할 수 없다.
    • 참고) sessionFixation().none()
      • 세션을 새로 생성하지 않고, 세션아이디도 새로 발급하지 않는다.
      • 따라서 none() 으로 하면, 세션 고정 공격에 당하게 된다.
    • 참고) 직접 설정하지 않더라도, 스프링 시큐리티가 초기화 되면서 기본값으로 작동되도록 처리한다.

 

👉 이제 코드로 작성해서 세션 고정 보호를 확인해보자.

  • SecurityConfig 수정 - none() 으로 적용.
    • none() 으로 설정했기 때문에, 공격자가 자신의 쿠키를 사용자의 브라우저에 심고, 사용자가 공격자의 쿠키로 인증을 받게 되면, 공격자는 인증을 받지 않고도, 동일한 쿠키로 접근을 해서 사용자의 정보를 공유할 수 있다.
  • 실행해보자. (공격자와 사용자 테스트를 위해 두 개의 서로 다른 브라우저를 띄우자.)
    • 왼쪽은 사용자, 오른쪽은 공격자라고 가정하자.
  • 공격자가 발급받은 JSESSIONID(8808***)를 복사해서, 사용자의 JSESSIONID에 붙여넣자.
    • 이제 사용자는 공격자의 쿠키를 가지고 있다.
  • 사용자가 인증을 시도한다.
    • 정상적으로 인증되었다.
    • 개발자 도구를 통해 쿠키 정보를 확인해보면, 그대로 공격자의 쿠키정보가 나오는 것을 확인할 수 있다.
  • 이제 공격자 페이지에서, (인증 받아야만 접근 가능한) 루트 경로에 접속해보자.
    • 원래라면 인증을 받아야하지만, 인증없이 자원에 접근 가능한 것을 확인할 수 있다.
    • 공격자는 사용자의 정보를 모두 공유할 수 있게 된다. => 세션 고정 공격

 

❗ 이런식으로 처리되면 보안상 큰 문제가 있다. 따라서 스프링 시큐리티의 기본값은 changeSessionId() 이다. 세션 고정 보호 정책을 변경해보자.

  • SecurityConfig 수정 - changeSessionId() 으로 적용.
  • 다시 동일한 방법으로 두 개의 서로 다른 브라우저를 띄우고 테스트해보자.
    • 공격자의 쿠키(0E5B***)를 복사해서 사용자의 쿠키를 공격자의 쿠키로 붙여넣자.
  • 사용자가 인증을 시도한다.
    • 정상적으로 인증된 것을 확인할 수 있다.
    • 그런데 이번엔 인증 후 공격자의 쿠키가 아닌 쿠키값이 변경되어있다. (세션아이디가 새롭게 발급된다.)
  • 공격자 브라우저에서 루트 경로로 접근해보자.
    • none() 으로 설정했을 때는 루트 경로 접근이 가능했지만, 이제는 인증이 되지 않았으므로 접근이 불가하다.

이번 내용에서는 세션 고정 보호 기능에 대해서 알아보았고, 세션 고정 보호 기능 API를 테스트하고 확인해 보았다.


이번엔 세션 정책에 대해서 알아보자.

✔️ 세션 정책

  • 참고)
    • http.sessionManagement() 을 작성함으로써 세션 관리 기능이 작동한다.
    • sessionCreationPolicy(..) 를 통해 세션 생성 정책을 작성할 수 있다.
    • (참고) 설정할 수 있는 값에는 총 4개가 있으며, 상세 설명은 위 이미지를 참고하자.
    • (참고) 세션을 사용하지 않는 인증 방식을 도입하고자 할때는 Stateless 속성을 세션 정책으로 사용한다. (ex. jwt 인증 방식)

 

✔️ 참고


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

profile
현실에서 한 발자국

0개의 댓글