이 글은 Spring Boot 3.x와 Security 6.x를 활용하여 중복 로그인을 관리하는 방법에 대해 설명합니다.
Spring Security의 세션 관리 기능을 사용하여 동일 사용자의 중복 로그인 시도를 제어하는 방법을 다룹니다.
중복 로그인은 보안 및 세션 관리 측면에서 중요한 문제입니다.
특히 Spring Boot와 Spring Security를 사용하여 안전하고 효율적으로 중복 로그인을 관리하는 방법에 대해 알아보겠습니다.
로그인 성공 시 세션에 사용자 정보 저장하기
먼저, 사용자의 로그인 정보를 세션에 저장하는 방법을 살펴보겠습니다.
Spring Security에서는 로그인 성공 시에 AuthenticationSuccessHandler를 통해 로그인 이후의 작업을 처리할 수 있습니다.
아래는 Ajax 방식으로 로그인을 처리하는 경우의 예시 코드입니다.
public class AjaxAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
// 로그인 성공시 ID 를 세션에 저장
HttpSession session = request.getSession();
session.setAttribute("user_email", authentication.getName());
objectMapper.writeValue(response.getWriter(), new ApiRes().responseMsg("Login Success"));
}
}
중복 로그인 관리를 위한 추상 클래스 및 구현 클래스 작성하기
중복 로그인을 관리하기 위해 추상 클래스와 이를 상속한 구현 클래스를 작성합니다.
이 예시에서는 Spring Session Storage와 Redis Session Storage 두 가지를 사용하여 중복 로그인을 관리하는 방법을 제시합니다.
@Component
public abstract class ConcurrentSessionManager {
public abstract void overlapManager(User user);
}
Spring Session Storage를 이용한 중복 로그인 관리 방법
@Component
@RequiredArgsConstructor
@Profile("local")
public class LocalConcurrentSessionManager extends ConcurrentSessionManager{
private final SessionRegistry sessionRegistry;
int duplicateLoginCnt = 3;
@Override
public void overlapManager(User user) {
List<SessionInformation> allSessions = sessionRegistry.getAllSessions(user.getEmail(), false);
int sessionSize = allSessions.size();
if (duplicateLoginCnt - sessionSize == 0) {
throw new CustomAuthenticationException(ExceptionType.EXCEED_DUPLICATION_LOGIN);
}
int expireCnt = sessionSize - duplicateLoginCnt + 1;
for (int i = 0; expireCnt > 0; i++) {
allSessions.get(i).expireNow();
expireCnt--;
}
}
}
Redis Session Storage 에서 중복체크 허용하는 방법
@Component
@RequiredArgsConstructor
@Profile("!local")
public class RedisConcurrentSessionManager extends ConcurrentSessionManager {
private final RedisTemplate<String, Object> redisTemplate;
int duplicateLoginCnt = 3;
@Override
public void overlapManager( User user) {
String userEmail = user.getEmail();
HashOperations<String, String, Object> hashOperations = redisTemplate.opsForHash();
Map<LocalDateTime, String> sessionAccessTimeMap = new HashMap<>();
Set<String> sessionKeys = redisTemplate.keys("spring:session:sessions:*");
if(sessionKeys != null){
for(String sessionKey : sessionKeys){
Map<String, Object> entries = hashOperations.entries(sessionKey);
String memberId = (String) entries.get("sessionAttr:user_email");
// 로그인 된 ID 찾기
if(memberId != null && memberId.equals(userEmail)){
// 세션 생성 시간 조회
Long creationTime = (Long) entries.get("creationTime");
sessionAccessTimeMap.put(
DateUtil.toLocalDateTimeOfEpochMilli(creationTime)
, sessionKey
);
}
}
// 중복 로그인 허용 횟수
int sessionSize = sessionAccessTimeMap.size();
if ((duplicateLoginCnt - sessionSize) == 0) {
throw new CustomAuthenticationException(ExceptionType.EXCEED_DUPLICATION_LOGIN);
}
// 중복 로그인 허용 횟수에 도달했다면 가장 오래된 세션을 만료시킨다. !
int expireCnt = (sessionSize - duplicateLoginCnt) + 1;
if(expireCnt > 0){
// 먼저 로그인 시도한 사용자 순으로 정렬
Map<LocalDateTime, String> sortedMap = new TreeMap<>(sessionAccessTimeMap);
List<String> sessionList = new ArrayList<>(sortedMap.values());
for(int i = 0; i < expireCnt; i ++){
redisTemplate.delete(sessionList.get(i));
}
}
}
}
}
결론
Spring Boot 3와 Spring Security 6에서는 Redis에 저장되는 세션 정보가 암호화되어 있어 직접 세션을 비교하기 어렵습니다.
이는 중복 로그인 관리를 비효율적으로 만들 수 있습니다.
127.0.0.1:6379> keys *
1) "spring:session:sessions:f7930e40-c77c-4047-9411-4985586a6b662"
2) "spring:session:sessions:f7930e40-c77c-4047-9411-1115286a6b662"
3) "spring:session:sessions:f7930e40-c77c-4047-9411-1593246a6b662"
참고로 이전 버전인 Spring Boot 2 에서는 세션 정보가 명시적으로 표시되어 있었기 때문에 세션 관리가 더 쉬웠습니다.
127.0.0.1:6379> keys *
1) spring:session:index:FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME:soobin1103@naver.com
2) spring:session:index:FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME:soobin1104@naver.com
3) spring:session:index:FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME:soobin1105@naver.com
▶ 암호화된 세션 정보와 중복 로그인 관리
Spring Boot 3와 Spring Security 6에서는 Redis에 저장되는 세션 정보가 암호화되어 있어 직접 세션을 비교하기 어렵습니다.
따라서 기존의 방법으로는 모든 세션을 조회하여 비교하는 것이 유일한 방법으로 보입니다.
하지만 이는 성능상의 문제가 발생할 수 있습니다.
▶ 더 나은 방법 탐색하기
현재로서는 모든 세션을 조회하는 방법이 유효한 해결책으로 보입니다.
그러나 지속적으로 Spring Boot 및 Spring Security의 업데이트를 주시하고 관련 문서를 참고하여 더 나은 방법이 제시되는지 확인하는 것이 중요합니다.
'개발중 > Spring Boot & Redis' 카테고리의 다른 글
Redis Pub/Sub 기반 SSE 실시간 알림 삽질 통해 구현하기. (0) | 2024.09.24 |
---|---|
Spring Boot 3와 Redis: 로컬 도커 환경에서 시작하기 (0) | 2024.05.08 |
Spring Boot와 Redis를 사용한 데이터 저장 및 관리 (0) | 2024.05.03 |
Spring Boot 2.x.x에서 3.x.x로 업그레이드 후 Redis에 Java 객체 저장 문제 발생 (0) | 2024.04.16 |
Spring boot 3.2.2 & Redis & GenericJackson2JsonRedisSerializer (0) | 2024.04.15 |