• 카테고리

    질문 & 답변
  • 세부 분야

    백엔드

  • 해결 여부

    해결됨

세션 동시성 제어 실시간 적용

20.10.06 02:22 작성 조회수 1.28k

0

안녕하세요 강사님, 좋은 강의 잘 듣고있습니다.

세션 동시성에 대해 질문드립니다.

동일한 계정으로 a client 에서 로그인 후 b client 로그인 시 a client 는 세션 만료가 되지만 a에서 server측에 연결 시도를 하지 않으면 invalidsession 표시가 안나오는데요,,

필터 특성상 어쩔수없다지만, 실시간으로 a client의 세션이 만료된걸 확인 할 수 있을까요?

답변 4

·

답변을 작성해보세요.

0

신충무님의 프로필

신충무

질문자

2020.10.09

궁금증 해결해주셔서 감사합니다^^

시큐리티 공부하다보니 궁금한게 많이 생기네요;

0

제가 위에서 설명한 부분은 b 클라이언트가 로그인하는 시점에 controller 에서 이전에 로그인한 a 클라이언트의 세션 만료 여부를 체크하는 구문을 예시로 작성한 것입니다.

그렇다고 해서 a 클라이언트에게 이런 정보가 전달되는 것은 아니죠

stateless, connectionless 등의 http 프로토콜 특성상 클라이언트가 서버로 요청하기 전에 서버가 클라이언트에게 이벤트를 전달하는 방법은 없습니다.

서버가 b클라이언트의 로그인 처리를 완료했다고 해서 이미 연결이 끊어진 a 클라이언트를 알 수 있는 방법이 없기 때문입니다.

a 클라이언트가 서버로 요청하기 전에  세션이 만료되었다는 이벤트를 서버가 전달할 수 있는 방법은 nodejs 에서의 socket.io 나 websocket 과 같이 서버에서의 실시간 push 개념을 도입하는 수 밖에 없습니다.

0

신충무님의 프로필

신충무

질문자

2020.10.08

성실한 답변 감사합니다^^

제가 확인하고싶은것은 sessioninformation 은 세션의 정보일 뿐 실제로 httpsession등을 만료하지 않는것으로 알고있는데요,

b클라이언트가 로그인시

기존 로그인되어있던 a 클라이언트가 아무런 이벤트를 하지 않아도 인지를 할 수 있는가?입니다.

a클라이언트가 이벤트를 발생하지 않는경우 http프로토콜을 타지않기 때문에 filter를 거치지도 않으니,,,

a 클라이언트가 실시간으로 알 수 있는 방법을 알고싶습니다,,,

서버에서 먼저 크라이언트에게 이벤트를 날릴 수 있을까요? websocket stomp등 사용하지 않고요,,

0

스프링 시큐리티에서 세션관련 정보를 생성, 삭제 등의 역할을 하는 클래스가 SessionRegistryImpl 입니다.

이 클래스에 보시면 여러가지 API 가 있는데요

List<Object> getAllPrincipals();

List<SessionInformation> getAllSessions(Object principal, boolean includeExpiredSessions);

SessionInformation getSessionInformation(String sessionId);

이 메서드를 활용해서 현재 활성화되어 있는 사용자들의 정보나 세션정보들을 얻을 수 있습니다.

그리고 또 하나의 클래스가 바로 SessionInformation 입니다.

이 클래스에 보시면 expiredNow() 메서드가 있는데 실제로 세션을 만료시킬 때

SessionManagementFilter 가 ConcurrentSessionControlAuthenticationStrategy 를 사용해서 

세션만료 대상자를 담고 있는 SessionInformation 의 expiredNow() 를 호출해서 만료설정을 하게 됩니다.

protected void allowableSessionsExceeded(List<SessionInformation> sessions,
int allowableSessions, SessionRegistry registry)
throws SessionAuthenticationException {

// Determine least recently used sessions, and mark them for invalidation
sessions.sort(Comparator.comparing(SessionInformation::getLastRequest));
int maximumSessionsExceededBy = sessions.size() - allowableSessions + 1;
List<SessionInformation> sessionsToBeExpired = sessions.subList(0, maximumSessionsExceededBy);
for (SessionInformation session: sessionsToBeExpired) {
session.expireNow();
}
}

그렇기 때문에 SessionRegistryImpl 와 SessionInformation 를 활용한다면 세션 관련 여러 정보들을 얻을 수 있습니다.

일단 SessionRegistryImpl 클래스를 빈으로 생성한 다음 DI 받은 후에 위의 API 들을 사용해서 현재 세션이 만료된 사용자들을 추출하시면 됩니다..

아래는 동일한 계정으로 생성된 세션정보를 모두 조회하는 SessionRegistryImpl 의  getAllSessions() 메서드입니다.

public List<SessionInformation> getAllSessions(Object principal,
boolean includeExpiredSessions) {
final Set<String> sessionsUsedByPrincipal = principals.get(principal);

if (sessionsUsedByPrincipal == null) {
return Collections.emptyList();
}

List<SessionInformation> list = new ArrayList<>(
sessionsUsedByPrincipal.size());

for (String sessionId : sessionsUsedByPrincipal) {
SessionInformation sessionInformation = getSessionInformation(sessionId);

if (sessionInformation == null) {
continue;
}

if (includeExpiredSessions || !sessionInformation.isExpired()) {
list.add(sessionInformation);
}
}

return list;
}

그러면 다음과 같이 사용할 수 있습니다.

public class SecurityConfig extends WebSecurityConfigurerAdapter {

protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/").permitAll()
.anyRequest().authenticated();
http
.formLogin();
http.sessionManagement()
.maximumSessions(1)
.sessionRegistry(sessionRegistry())
;
}

@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}

}

SessionRegistry 를 빈으로 등록하고 sessionManagement 에 등록합니다.

로그인 성공 후 AuthenticationSuccessHandler 에서 메인으로 Redirect 하고 Controller 에서 SessionRegistry 를 DI 하고 해당 계정으로 생성된 모든 세션 정보들을 조회하는 구문입니다.

@RestController
public class SecurityController {

@Autowired
private SessionRegistry sessionRegistry;

@GetMapping("/")
public String invalidSession(){

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

List<SessionInformation> allSessions = sessionRegistry.getAllSessions(authentication.getPrincipal(), true);

for (SessionInformation session: allSessions) {
System.out.println("사용자:" + ((User)session.getPrincipal()).getUsername());
System.out.println("세션아이디:" + session.getSessionId());
System.out.println("세션만료여부:" + session.isExpired());
System.out.println("=======================================================");
}

return "home";
}
}

이것은 하나의 예시를 보여 드린 것이고 이 외에도 위의 정보들을 활용하시면 다양하게 세션관련 정보들을 사용하실 수 있습니다.

그리고 다만 한가지 기억하실 점은 중복 세션이 발생한 상황에서 a client 가 서버로 요청을 하게 되면ConcurrentSessionFilter 를 먼저 타게 되고 로그아웃 처리가 되어 세션이 무효화되기 때문에 세션이 만료된 상황을 실시간적으로 조회 할 수 없게 됩니다.

그래서 서버로 다시 요청하기 전에 해당 정보들을 DB 혹은 메모리에 저장을 해서 ConcurrentSessionFilter 를 탄 이후라도 조회가 가능하도록 별도의 구현이 필요합니다.