이 글은 정수원님의 Infrean 강의를 학습한 내용을 정리하여 작성합니다.
인증 API - 동시 세션 제어
동시 세션 제어
- 동일한 계정으로 인증을 받을 경우 생성되는 세션의 허용 개수가 초과되었을 때 제어하는 방법을 의미한다.
- 두 가지 전략
- 이전 사용자 세션 만료
- 현재 사용자 인증 실패
- 동일한 계정으로 생성할 수 있는 세션의 허용 개수를 한 개라 가정한다.
- 사용자1과 사용자2는 서로 동일한 계정으로 로그인한다고 가정한다. (세션 2개: 최대 세션 허용 개수 초과)
- 이전 사용자 세션 만료
- 이전 사용자 (사용자1)가 링크로 접속해 접근할 때 세션을 만료시킨다.
- 최대 세션 허용 개수 1개를 유지한다.
- 현재 사용자 인증 실패
- 동일한 계정으로 이미 사용자1이 세션을 유지 중이므로 사용자2는 인증 예외가 발생한다.
- 최대 세션 허용 개수 1개를 유지한다.
- 이전 사용자 세션 만료
http.sessionManagement(); // 세션 관리 기능이 작동한다.
- maxSessionsPreventsLogin() 메소드 파라미터 값으로 true를 사용하면 동시 로그인이 차단된다.
- 동시 세션 제어 두 번째 방식: 현재 사용자 인증 실패를 적용하는 방식이다.
- invalidSessionUrl(), expiredUrl() 메소드가 동시에 적용된 경우 invalidSessionUrl() 메소드가 우선된다.
[참고]
위 그림처럼 invalidSessionUrl() 메소드를 위치하면 컴파일 오류가 발생한다.
다음과 같이 위치하면 컴파일 오류가 발생하지 않는다.
실습
실행
- 두 개의 웹 브라우저를 띄우고 동시에 로그인을 수행한다.
- 경고 문구가 출력되는 것을 확인할 수 있다.
- maxSessionsPreventsLogin() 메소드 파라미터 값을 false로 변경한다.
실행
- 두 사용자 모두 인증에 성공하는 것을 확인할 수 있다.
- 하지만 현재 최대 허용 가능 세션 수가 1이므로 이전 사용자의 세션이 만료되었을 것이다.
- 다음과 같이 문구가 출력되는 것을 확인할 수 있다.
인증 API - 세션 고정 보호
- 위 그림과 같이 사용자와 공격자가 동일한 JSESSIONID를 사용해 정보를 공유하는 것을 세션 고정 공격이라 한다.
- 해결방법
- 인증에 성공할 때마다 새로운 세션이 생성되고 새로운 JSESSIONID를 생성하도록 한다.
http.sessionManagement(); // 세션 관리 기능이 작동한다.
- changeSessionId() 기본값 (서블릿 3.1 이상)
- 사용자의 세션은 그대로 두고 JSESSIONID만 변경된다.
- migrateSession() (서블릿 3.1 이하)
- 새로운 세션을 생성하고 새로운 JSESSIONID를 발급한다.
- newSession()
- 세션과 JSESSIONID가 새로 발급되지만 이전 세션에서 설정한 속성값을 그대로 사용하지 못하고 새로 설정해야 한다.
- none
- 세션과 JSESSIONID가 새로 생성되지 않고 그대로 세션 고정 공격을 받게 된다.
실습
@Configuration
public class SecurityConfig {
@Autowired
UserDetailsService userDetailsService;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authz) -> authz
.anyRequest().authenticated()
)
.httpBasic(withDefaults());
...
http
.formLogin()
.and()
.sessionManagement()
.sessionFixation().none();
return http.build();
}
}
실행 none()
- 세션 고정 공격 상황을 가정한다.
- 사용자 (왼쪽)
- 공격자 (오른쪽)
- 우선 공격자가 먼저 우리의 서버에 접근해 다음과 같이 JSESSIONID를 획득한다.
- 공격자는 다음과 같이 사용자의 JSESSIONID를 자신이 발급받은 JSESSIONID로 변경한다.
- 공격자의 JSESSEIONID를 가진 상태로 로그인을 수행해도 그대로 JSESSIONID가 유지된다.
- 그 후 공격자가 인증을 거치지 않은 상태에서 루트 페이지로 이동한 결과는 다음과 같다.
- 인증 과정 없이 서버에 접속 가능하다.
- 공격자는 사용자 정보를 공유 가능하다.
@Configuration
public class SecurityConfig {
@Autowired
UserDetailsService userDetailsService;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authz) -> authz
.anyRequest().authenticated()
)
.httpBasic(withDefaults());
...
http
.formLogin()
.and()
.sessionManagement()
.sessionFixation().changeSessionId();
return http.build();
}
}
실행 changeSessionId()
- 사용자의 JSESSIONID를 공격자의 JSESSIONID로 변경한다.
- 공격자의 JSESSIONID를 가진채로 사용자는 로그인을 수행한다.
- 로그인 후 사용자의 JSESSIONID를 확인하면 기존과 다른 것을 확인할 수 있다.
JSESSIONID가 더이상 공격자의 JSESSIONID가 아닌 것을 확인할 수 있다.
- 그 상태로 공격자가 루트 페이지로 접근하면 다음과 같다.
공격자는 인증을 받지 않아 루트 페이지로 접근이 불가능하다.
인증 API - 세션 정책
http.sessionManagement(); // 세션 관리 기능이 작동한다.
- 세션을 사용하지 않는 인증 방식을 사용하고자 하는 경우 SessionCreationPolicy.Stateless 방식을 사용해야 한다.
- 예를 들어
- JWT 방식과 같이 세션 사용 없이 토큰에 모든 사용자의 정보나 추가 정보를 저장하고 인증 받는 경우
'스프링 시큐리티 > 스프링 시큐리티 기본 API 및 Filter 이해' 카테고리의 다른 글
권한설정과 표현 (0) | 2023.01.27 |
---|---|
세션 제어 필터: SessionManagementFilter, ConcurrentSessionFilter (0) | 2023.01.11 |
익명 사용자 인증 필터: AnonymousAuthenticationFilter (0) | 2023.01.11 |
Remember Me 인증 필터: RememberMeAuthenticationFilter (0) | 2023.01.10 |
Remember Me 인증 (0) | 2023.01.10 |