[스프링 시큐리티] 4. 기본 API 및 Filter 이해(동시 세션 제어/세션 고정 보호/세션 정책)
해당 포스팅은 인프런에서 스프링 시큐리티 정수원님의 강의를 참고하여 작성했습니다.
스프링 시큐리티 - Spring Boot 기반으로 개발하는 Spring Security - 인프런 | 강의
동시 세션 제어
우리가 사용하는 웹서비스 중에 동일한 계정으로 동시에 로그인하는 것을 허용하지 않는 경우가 있습니다.(일반적으로 유료 강의를 제공하는 사이트가 허용하지 않죠.)
이러한 것을 중복 로그인을 허용하지 않는 것이라고 말하는데, 어떻게 중복으로 로그인 했는지 알까요?
동시 세션 제어를 통해서 중복 로그인을 방지할 수 있습니다.
그렇다면 스프링 시큐리티는 어떻게 이를 처리 할까요?
동시 세션 제어 과정
- 동일한 계정으로 인증을 받을때 생성되는 세션의 허용 갯수를 유지하거나 만료시키는 정책을 의미 합니다.
정책은 2가지가 있습니다.
2가지 정책 모두 동일한 계정을 사용하고, 최대 세션 허용 갯수를 초과했다고 가정 합니다.
- 이전 사용자 세션 만료
- 현재 사용자 인증 실패
이전 사용자 세션 만료
maxSessionsPreventsLogin(false)
MySecurityConfig
현재 사용자 인증 실패 정책을 코드로 표현하면 다음과 같습니다.
@Configuration
@EnableWebSecurity
class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated();
http
.formLogin();
http
.sessionManagement() // 세션 관리 기능이 작동
//.invalidSessionUrl("/invalid") // 세션이 유효하지 않을 때 이동할 페이지
.maximumSessions(1) // 최대 허용 가능 세션 수
.maxSessionsPreventsLogin(false) // true: 동시 로그인 차단, false: 기존 세션 만료
//.expiredUrl("/expired"); // 세션이 만료된 경우 이동 할 페이지
}
}
MySecurityController
@Slf4j
@RestController
public class MySecurityController {
@GetMapping("/")
public String index(HttpSession session) {
return "home";
}
}
2개의 웹브라우저를 띄우고 테스트를 해보면 다음과 같습니다.
- 첫번째 브라우저에서 로그인을 합니다.
- 두번째 브라우저에서 로그인을 시도합니다.
- 첫번째 브라우저에서 새로고침을 눌러봅니다.
- 로그아웃된 것을 확인 할 수 있습니다.
현재 사용자 인증 실패
maxSessionsPreventsLogin(true)
MySecurityConfig
현재 사용자 인증 실패 정책을 코드로 표현하면 다음과 같습니다.
@Configuration
@EnableWebSecurity
class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated();
http
.formLogin();
http
.sessionManagement() // 세션 관리 기능이 작동
//.invalidSessionUrl("/invalid") // 세션이 유효하지 않을 때 이동할 페이지
.maximumSessions(1) // 최대 허용 가능 세션 수
.maxSessionsPreventsLogin(true) // true: 동시 로그인 차단, false: 기존 세션 만료
//.expiredUrl("/expired"); // 세션이 만료된 경우 이동 할 페이지
}
}
2개의 웹브라우저를 띄우고 테스트를 해보면 다음과 같습니다.
- 첫번째 웹브라우저를 통해서 로그인을 시도 합니다.
- 두 번째 브라우저를 통해 로그인을 시도합니다.
- 로그인에 실패 합니다.
세션 고정 보호
세션 공격은 공격자가 먼저 세션 ID를 받은 후, 세션 ID를 사용자에게 심어두어 사용자가 그 세션 ID로 인증하도록 유도하는 방법 입니다.
이러한 세션 공격을 막기 위해서 세션 고정 보호가 생겼습니다.
인증 할 때마다 새로운 세션과 쿠키를 생성하여 공격자가 사용자의 세션 ID를 공유할 수 없게 만드는 것 입니다.
세션 공격 과정
MySecurityConfig
@Configuration
@EnableWebSecurity
class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated();
http
.formLogin();
http
.sessionManagement()
.sessionFixation().none();
// changeSessionId(): 세션 아이디 변경(default),
// none(): 세션 고정 보호 미설정,
// migrateSession(): 서블릿 3.1 이하에서 changeSessionId() 작동하도록,
// newSession(): 새로운 세션 생성
}
}
세션 공격 시뮬레이션
- 공격자가 웹 어플리케이션에 접속 합니다.
JSESSIONID
를 발급 받은 것을 확인 합니다.
- 공격자가 발급받은
JSESSIONID
를 복사하여 사용자의JSESSIONID
로 붙여넣습니다.
- 로그인을 시도 합니다.
- 로그인이 완료되면
localhost:8080/
으로 이동합니다.
- 로그인이 완료되면
- 공격자가
localhost:8080/
에 접속합니다.- 공격자가 로그인을 하지 않고 서버의 자원을 이용 할수 있습니다..!!!
MySecurityConfig
세션 공격을 방지하기 위해서
로그인할때 마다 JSESSIONID
를 변경 합니다.
이 방법을 세션 고정 보호라고 합니다.
@Configuration
@EnableWebSecurity
class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated();
http
.formLogin();
http
.sessionManagement()
.sessionFixation().changeSession();
// changeSessionId(): 세션 아이디 변경(default),
// none(): 세션 고정 보호 미설정,
// migrateSession(): 서블릿 3.1 이하에서 changeSessionId() 작동하도록,
// newSession(): 새로운 세션 생성
}
}
- 공격자가 웹어플리케이션에 접속하여
JSESSIONID
를 발급받습니다.
- 공격자의
JSESSIONID
을 복사하여 사용자의JSESSIONID
에 붙여 넣습니다.
- 사용자가 로그인을 합니다.
JSESSIONID
가 변경된 것을 확인 할 수 있습니다.
- 공격자가 심어 놓은
JSESSIONID
가 변경되었기 때문에 사용자의 인증 정보로 서버의 자원에 접근 할수 없습니다.
세션 정책
SessionCreationPolicy.IF_REQUIRED
- 스프링 시큐리티가 필요시 세션 생성(
default
)
- 스프링 시큐리티가 필요시 세션 생성(
SessionCreationPolicy.ALWAYS
- 스프링 시큐리티가 항상 세션 생성
SessionCreationPolicy.NEVER
- 스프링 시큐리티가 생성하지 않지만 이미 존재하면 사용
SessionCreationPolicy.STATELESS
- 스프링 시큐리티가 생성하지 않고 존재해도 사용하지 않음(
JWT
)
- 스프링 시큐리티가 생성하지 않고 존재해도 사용하지 않음(
@Configuration
@EnableWebSecurity
class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated();
http
.formLogin();
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
/**
* SessionCreationPolicy.IF_REQUIRED : 스프링 시큐리티가 필요시 세션 생성 (default)
* SessionCreationPolicy.ALWAYS : 스프링 시큐리티가 항상 세션 생성
* SessionCreationPolicy.NEVER : 스프링 시큐리티가 생성하지 않지만 이미 존재하면 사용
* SessionCreationPolicy.STATELESS : 스프링 시큐리티가 생성하지 않고 존재해도 사용하지 않음 (JWT)
*/
}
}
정리