| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | ||||
| 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 11 | 12 | 13 | 14 | 15 | 16 | 17 |
| 18 | 19 | 20 | 21 | 22 | 23 | 24 |
| 25 | 26 | 27 | 28 | 29 | 30 | 31 |
- 쿠키
- LocalDate
- spring security 설정
- 오버로딩
- 자바의 정석
- 프로그래머스 둘만의 암호
- 둘만의 암호
- 멀티프로세싱
- SQL Mapper
- localtime
- CPU
- over()
- 입출력
- 멀티태스킹
- 티스토리챌린지
- 혼공얄코
- 백트래킹
- hackerrank
- spring security
- 리눅스
- 자바의정석
- StringBuilder
- 캡슐화
- java
- 오블완
- StringBuffer
- 다형성
- 둘만의 암호 자바
- 프로그래머스
- 오버라이딩
- Today
- Total
쉽게 쉽게
[Spring] HttpSessionListener을 이용한 중복로그인 방지 본문
▤ 목차
1. 중복로그인 방지를 위한 방법
Spring을 사용하면서 중복로그인을 방지하기 위한 방법은 여러 가지가 있다.
그 중 대표적인 3가지 방법은 이렇다.
1. Spring Security 설정 (실패)
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.sessionManagement()
.maximumSessions(1) // 동시에 허용할 세션 수
.maxSessionsPreventsLogin(true) // true: 새로운 로그인 차단 / false: 기존 세션 만료
.expiredUrl("/login?expired"); // 기존 세션이 만료됐을 때 이동할 URL
}
제일 간단한 방법으로 Security 설정에는 중복로그인을 방지하는 방법이 이미 구현되어 있다.
sessionManagement의 maximumSessions을 통해 세션을 관리할 수 있다.
- maximumSessions(숫자): "동시에 허용할 세션 수"
- maxSessionsPreventsLogin(boolean): true: 새로운 로그인 차단 / false: 기존 세션 만료
허용 세션 수를 1로 설정하고 새로운 로그인에 대한 설정을 진행하면 끝이다.
하지만 레거시 이슈로 인해 적용이 불가했다.
2. Spring Security의 SessionRegistry를 통해 세션을 관리 (실패)
@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
@Autowired
private SessionRegistry sessionRegistry;
public void printSessions() {
//사용자의 세션 목록 조회
for (Object principal : sessionRegistry.getAllPrincipals()) {
List<SessionInformation> sessions = sessionRegistry.getAllSessions(principal, false);
System.out.println("User: " + principal + " / Active sessions: " + sessions.size());
}
// ... 중복로그인 방지 코드 ...
}
구현되어 있는 설정을 활용하는 것이 안된다면 Security의 SessionRegistry를 활용하여 직접 세션 관리를 해보려고 했다.
SessionRegistry 인터페이스의 기본 구현체가 SessionRegistryImpl이며, SessionRegistryImpl는 Spring Security에서 세션 정보를 관리하는 구현체다.
SessionRegistryImpl는 아래와 같은 메서드들을 활용할 수 있다.
| registerNewSession(String sessionId, Object principal) | 새로운 세션이 생성되었을 때 등록 |
| removeSessionInformation(String sessionId) | 세션이 무효화되었을 때 제거 |
| getAllPrincipals() | 현재 등록된 모든 사용자(principal) 목록 반환 |
| getAllSessions(Object principal, boolean includeExpiredSessions) | 특정 사용자의 세션 목록 조회 |
| refreshLastRequest(String sessionId) | 세션 마지막 요청 시간을 갱신 |
세션 목록을 조회하여 중복되는 사용자의 세션을 만료하는 코드를 짜고자 했다.
하지만 이 또한 레거시 이슈로 인해 적용 불가했다.
레거시 문제가 없는 프로젝트에서는 두 방법을 꼭 써봐야겠다...
2. HttpSessionListener을 이용한 중복로그인 방지 (성공)
위와 같은 이유들 때문에 Security를 활용하여 세션을 관리하기보다는 직접적인 세션을 관리하는 방식을 선택했다.
1. HttpSessionListener란?
HttpSessionListener는 세션 생성 및 세션 제거 이벤트를 처리한다.
- sessionCreated: 이 이벤트는 사용자가 처음 웹 애플리케이션에 접속할 때 발생한다. 즉 브라우저가 켜지는 순간 이벤트가 발생된다.
- sessionDestroyed: 이 이벤트는 세션이 만료되거나(로그아웃, 타임아웃) 무효화될 때 호출된다.
이 중 sessionDestroyed 메서드를 이용해서 중복로그인 방지를 구현하려고 한다.
2. HttpSessionListener를 활용한 중복로그인 구현
구현 및 동작 순서
- HttpSessionListener를 구현하는 SessionConfig 클래스를 생성(세션 생성, 종료시 동작)
- SecurityUtil에 SessionConfig에서 사용하는 메서드들을 정의
- 로그인 성공시 CustomLoginSuccessHandler(로그인 성공핸들러)에서 세션저장
- sessionDestroyed에서 중복로그인 시도 시 기존의 로그인된 세션 제거(세션은 항상 최신상태것만 존재)
- CustomLoginSuccessHandler에서 다시 최신의 세션을 저장
/**
* 중복로그인 방지를 위한 세션 관리
*/
@WebListener
public class SessionConfig implements HttpSessionListener {
@Autowired
private SecurityUtil SecurityUtil;
@Override
public void sessionCreated(HttpSessionEvent se) {
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
String sessionId = se.getSession().getId();
String userId = "";
// 세션 ID를 이용해 관리 맵에서 사용자 ID를 찾음
try {
userId = SecurityUtil.getSessionId(sessionId);
}catch(NullPointerException e){
userId = null;
}
if (userId != null) {
// 해당 사용자의 정보를 관리 맵에서 제거
SecurityUtil.removeSession(userId);
}
}
}
@WebListener 어노테이션이란?
@WebListener는 Servlet 3.0 이상에서 제공하는 어노테이션으로 리스너(Listener)를 등록할 때, web.xml에 설정하지 않고 자동으로 실행이 되도록 해준다. (web.xml에 listener를 등록하는 것과 동일한 의미)
리스너란?
리스너는 서블릿 컨테이너에서 발생하는 이벤트를 감지하고 처리하는 객체
즉 아래와 같은 이벤트들에 대해 다루는 객체들을 의미한다.
- 세션 생성 / 종료 감지
- 서블릿 컨텍스트 초기화 / 소멸 감지
- 요청(request) 시작 / 종료 감지
대표적인 리스너들은 이렇다.
- 서블릿 컨텍스트 관련
- ServletContextListener
- ServletContextAttributeListener
- 세션 관련
- HttpSessionListener
- HttpSessionAttributeListener
- HttpSessionIdListener
- 요청 관련
- ServletRequestListener
- ServletRequestAttributeListener
만약 @WebListener가 없다면 web.xml에 이렇게 설정해야 했다.
<listener>
<listener-class>com.example.SessionConfig</listener-class>
</listener>
/**
* 중복로그인 방지를 메서드 정의
*/
public class SecurityUtil {
// userId를 키, HttpSession 객체를 값으로 저장
private static final Map<String, HttpSession> loginUsers = new ConcurrentHashMap<>();
// 새로운 로그인 시 기존 세션을 무효화하는 로직(중복로그인 방지)
public void addNewLoginSession(String userId, HttpSession newSession) {
// userId에 해당하는 기존 세션이 있는지 확인
HttpSession existingSession = loginUsers.get(userId);
if (existingSession != null && !existingSession.getId().equals(newSession.getId())) {
// 동일한 로그인 ID에 다른 세션 ID를 가진 기존 세션이 있다면 무효화
try {
existingSession.invalidate();
} catch (IllegalStateException e) {
// 이미 만료된 세션일 경우 예외 처리
}
}
// 새로운 세션을 맵에 저장 (기존 세션이 있다면 덮어쓰기)
loginUsers.put(userId, newSession);
}
// 로그아웃 또는 세션 만료 시 호출
public void removeSession(String userId) {
loginUsers.remove(userId);
}
// 세션 ID로 사용자 ID 찾기 (sessionDestroyed에서 사용)
public String getSessionId(String sessionId) {
for (Map.Entry<String, HttpSession> entry : loginUsers.entrySet()) {
if (entry.getValue().getId().equals(sessionId)) {
return entry.getKey();
}
}
return null;
}
}
/**
* 로그인 성공시 이벤트 핸들러
*/
public class CustomLoginSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(
HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
//CustomUser클래스는 spring security에서 제공해주는 로그인 객체를 커스텀한 것
//즉 그냥 로그인한 ID를 가져오는 코드라고 생각하면 된다.(구현방식마다 다 다를듯)
CustomUser user = (CustomUser) authentication.getPrincipal();
// 로그인 성공 시, 세션 관리자에게 세션 등록을 요청(id와 세션을 저장)
securityUtils.addNewLoginSession(user.getAdminId(), request.getSession());
...생략...
}
}
3. 구현시 이슈사항
https://myhappyman.tistory.com/116
Spring - HttpSessionListener 로그인 세션 관리(중복 로그인 방지하기)
웹을 하다보면 자주 접하는 중복 로그인 방지 기능 요청사항이 들어오는데, HttpSessionListener를 통해 관리할 수 있습니다. 해당 객체는 Session이 생성되거나 제거될때 발생하는 이벤트를 제공하므
myhappyman.tistory.com
https://magicmk.tistory.com/82
HttpSessionListener를 이용한 중복 로그인 방지
프로젝트를 간단하게 진행하다 보니 중복 로그인에 관한 내용을 신경 쓰지 않고 있었다. Spring Security를 이용하면 수월하다고 하던데프로젝트에 Spring Security를 적용하지 않아서 급한대로HttpSessio
magicmk.tistory.com
구현하면서 참고했던 블로그이지만 아래와 같은 이슈가 있어 몇 번의 수정이 필요했다.
- 세션은 브라우저가 켜지는 순간 생성되므로 로그인하지 않아도 sessionCreated가 동작하게 된다. 때문에 세션 저장을 sessionCreated 이벤트에 하게되면 세션 정보가 꼬일 수가 있다.
- 이미 세션이 만료된 아이디를 한 번 더 세션을 만료시킬 경우 IllegalStateException가 발생하게 된다. 이를 try-catch문을 통해 방지했다.
- 또한 sessionDestroyed에서도 중복로그인 검증 시 조회되는 id가 없을 경우에도 NullPointerException이 발생했기에 이를 try-catch문으로 방지했다.
| 잘못된 내용이 있다면 지적부탁드립니다. 방문해주셔서 감사합니다. |

'Spring > Spring' 카테고리의 다른 글
| [Spring] Spring Security 구현 (3) - 활용 (0) | 2024.12.20 |
|---|---|
| [Spring] Spring Security 구현 (2) - 커스텀 (1) | 2024.11.05 |
| [Spring] Spring Security 구현 (1) - 설정 (4) | 2024.10.31 |
| [Spring] tiles 적용 (5) | 2024.09.05 |
| [Spring] static 변수에 autowired 설정 방법 (0) | 2024.07.29 |
