동시 세션 제어
> 사용자들이 서버에 인증을 받을 때, 서버에서 허용하는 동일한 계정에 대한 최대 접속 허용 갯수가 있음.
> 만약에 n개까지 허용하고 현재 n 개의 세션이 저장되어 있는데, 한 명이 더 요청을 해서 세션을 형성시켜야 하는 경우, Security는 크게 두가지 전략으로 이에 대응한다.
1안 : 이전 사용자 세션 만료
사용자1의 세션은 서버단에서 만료시키는 것. 그래서 사용자2가 세션을 형성했을 경우, 사용자1이 다시 자원요청을 할 때, 서버에선 사용자1의 요청을 들어주지 않고 다시 인증 페이지로 유도한다.
2안 : 현재 사용자 인증 실패
단순하게 세션이 모두 차 있으니, 새로운 세션 형성을 막아서 로그인을 못하게 한다. 이건 좀 이상한 듯. 이렇게 되면 사용자1의 세션이 만료될 때까지 아무도 접근할 수 없는 것..?
Security 에선 이 또한 직접 설정하여 어떻게 관리할지 선택할 수 있다.
{
...
http
.sessionManagement()
.maximumSessions(1) // 허용할 세션 갯수, 테스트를 위해 1로 지정
.maxSessionsPreventsLogin(false) // 2번안을 선택할 것인지
.expiredUrl("/login") // 만료된 세션이 경우 이동할 곳
;
}
브라우저 두개 띄워서 테스트를 수행해볼 수 있다. 이 때, maxSessionsPreventsLogin(false)로 설정할 경우 다른 브라우저는 로그인이 안된다. 로그를 확인해보면 Max Sessions 어쩌구 이게 뜬다. mspL(true)로 할 경우 다른 브라우저로 로그인은 되나, 먼저 로그인한 브라우저를 refresh 해보면 로그아웃 되어 있음.
2번안을 선택여부에 대해서는 ConcurrentSessionControlAuthenticationStrategy.java 클래스에서 exceptionIfMaximumExceed 값을 변경한다. 이 클래스에서 session Cnt 를 판단하는 로직이 들어가있음을 알 수 있다.
====== 세션 고정 보호 정책 추가 설정 가능
해당 정책은 세션 고정 공격에 대해서 방어하는 정책.
1. 공격자가 접속을 시도해서 통신으로 인해 받은 SESSIONID를 보유.
2. 공격자가 특정 방법을 통해 사용자의 통신에 자신의 SESSIONID를 심는다.
3. 사용자가 로그인을 하면 서버는 해당 SESSIONID에 대한 SESSION 객체를 형성해서 인증을 한다.
4. 공격자가 자신의 SESSIONID를 그대로 사용하면 사용자에 대한 정보에 접근 가능하다 (서버는 사용자인줄 알기 때문)
{
...
http
.sessionManagement()
.sessionFixation().changeSessionId() // 기본 값
;
}
다음과 같은 설정으로 보안 가능. 사용자가 인증을 성공하게 되면, 그 세션은 그대로 두고, 세션 ID만이 변경이 된다. 그러면 사용자는 새로 발급받은 세션ID를 받아와서 그 ID를 쓰게 되고, 공격자는 자신의 세션ID로는 더이상 해당 세션을 받아올 수가 없다.
========== 세션 정책
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.하기 4개 중 하나)
;
SessionCreationPolicy 설정 가능, 4가지가 있음.
1. Always - 항상 생성한다.
2. If_Required (기본값) - 필요할 때 세션을 형성한다
3. Never - 생성하지는 않지만, 존재하면 사용을 한다
4. Stateless - 개발자들이 알아서 세션을 관리하도록 함. Security 는 생성하지 않고, 존재해도 사용하지 않음. 가령, JWT 인증 방식을 자체적으로 구현하고 싶으면, JWT를 구현시킨 다음에 Security의 세션은 꺼주면 됨.
========== Spring Security 의 세션 관련 필터 (이거 재밌음)
1. Session Management Filter
> 우리가 설정한 세션 메니지먼트 관련해서 역할을 수행
1. 세션 관리 : 인증시 사용자의 세션 정보를 등록 / 조회 / 삭제 등 관리
2. 동시 세션 제어 : 동일 계정으로 접속이 허용되는 최대 세션 수를 제한 // 동일한 세션에 대한 관리
3. 세션 고정 보호 : 인증할 때 마다 세션 ID를 담은 세션 쿠키를 새로 발급한다.
4. 세션 생성 정책 : 위에 4가지
2. ConcurrentSessionFilter
> 매 요청마다 현 사용자의 세션 만료 여부 체크 / 세션 만료 판단시 즉시 로그아웃, 오류 응답 (session.isExpired() == true)
두 필터가 같이 동시 세션 제어를 제어하는 모습
<4분 후반 그림 >
동시 세션 전략 중 이전 사용자 세션 만료 전략.
>> SM Filter가 이전 사용자에 대한 session.expireNow() 처리를 한다.
>> 따라서 이전 사용자가 다시 접근 요청시 CS Filter 가 ㄴㄴ 함.
<7분 30초 그림>
2번 필터 = ConcurrentSessionFilter
3,4,5번 클래스들 == SessionManagementFilter
1번 필터 >> 가장 먼저 5번 클래스를 호출한다. 5번 클래스는 동시적 세션 제어를 Control 함. 현재 인증을 시도하는 계정 세션의 카운트가 얼마인지 확인한다. 첫 요청이므로 아무런 문제 없이 통과함.
두번째로 4번 클래스로 감. 이름만 봐도 알 수 있듯이 세션 고정 보호 클래스임. 따라서 새롭게 세션 ID를 발급받고 쿠키를 생성함. 그리고 3번 클래스로 감. 3번 클래스에서는 사용자 세션과 새로 발급한 세션 ID를 연결해준다. 세션 정보 등록 1 로 저장이 됨. 그리고 첫 인증을 마친다.
같은 계정으로 누군가가 다른 브라우저로 접속했다.
>> 5번 클래스로 감. 내가 설정한 maxSessions == 현재 세션이다. 두 전략이 있던거 기억? 1. 인증 실패, 2. 전사용자 만료.
인증 실패 > 그냥 Exception 처리 바로 함. (추후 처리 안해도 됨, 바로 SPring 내 Exception Handler로 감)
전 사용자 만료 > 사용자 2는 인증 성공해서 위와 같이 4번 클래스로 보낸다. 사용자1은 expireNow = true 로 set 한다
--------------- 사실 이 떄가지는 2개의 세션으로 스프링은 알고 있음. 하나는 expire 예정일 뿐
>> 이후 사용자 1 이 자원요청 : 바로 2번 필터를 통과함. 2번 필터는 그냥 항상 통과함. 이 때 2번 필터는 자신의 세션 만료가 예정이 되어 있었는지를 판단함. 즉 위에서 session.expireNow() == true로 설정 되어 있으므로, 2번 필터는 즉시 로그아웃 처리를 해준다. >> 그리고 expired MSG 를 응답해준다. 그리고 핸들링을 해주면 됨.
5번 클래스로 들어가보면 허용 갯수 초과시 allowableSessionsExceeded 로 이동하는 것을 볼 수 있는데, 여기서 마지막에 sessionsToBeExpired() 는 session.expireNow() 처리를 해주는 것을 볼 수 있음. Comparator Sorting 을 통해서 최근 세션 판단함 ㅋㅋㅋ. 이게 다 까보면 ㅅㅂ ㅋㅋㅋ 다 그냥 진짜 친숙한 로직들 쓰고 있음 ㅋㅋㅋㅋㅋ
Concurrentfilter 도 찍어서 확인해보면 자신의 세션 상태를 expiredNow 를 체크하는지 확인하는 것을 볼 수 있음.
=======
웹 로그인의 기본 방식인 Session 관리 방식에 대해서, Spring Security 가 지원해주는 API를 확인할 수 있습니다.
세션 방식은 서버의 메모리 부하를 줄 가능성이 있는 방식이기 때문에 최근에는 토큰 기반 인증을 많이 사용하는 추세입니다. 하지만 언제든 기본이 되는 방식에 대해서 잘 알고 있는 것이 중요! 직접 다 구현하려면 이거 어려운거임!