자바 서블릿 패키지에서 제공하는 HttpSessionListener 인터페이스는 세션의 생성과 소멸에 대해서 이벤트를 감지하도록 되어있다.
아래의 사진을 살펴보면 2개의 생성, 소멸과 관련된 메소드가 존재한다.
해당 인터페이스를 상속받는 클래스를 만들어주면 세션이 생성 될 때와 소멸될 때의 세션값을 보관해서 소위 말하는 "중복로그인"이 되지 않는 기능을 만들어 볼 수 있다.
먼저 해당 인터페이스를 상속받자.
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
public class SessionConfig implements HttpSessionListener{
@Override
public void sessionCreated(HttpSessionEvent hse) {
}
@Override
public void sessionDestroyed(HttpSessionEvent hse) {
}
}
그리고 세션이 생성될 때의 해당 세션을 기록하기 위한 Map객체를 추가하여주자.
세션이 생성되면 Map객체에 고유 키 값과 session값을 넣어주도록 하자.
마찬가지로 세션이 사라지게 될 경우 Map객체에서 해당 세션을 제거하도록 하여주자.
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
public class SessionConfig implements HttpSessionListener{
private static final Map<String, HttpSession> sessions = new ConcurrentHashMap<>(); //세션을 담을 객체
@Override
public void sessionCreated(HttpSessionEvent hse) {
sessions.put(hse.getSession().getId(), hse.getSession()); //getId메소드는 해당 세션의 고유 키값
}
@Override
public void sessionDestroyed(HttpSessionEvent hse) {
sessions.get(hse.getSession().getId()).invalidate();
sessions.remove(hse.getSession().getId());
}
}
여기까지 하였다면 작업은 거의 끝났다고 볼 수 있다.
세션이 생성될 때는 사용자가 웹 브라우저를 통해서 해당서버로 접속할 때 바로 생성이된다.
그리고 통상적으로 로그인을 하게되는 경우 세션에 로그인을 하는 아이디값이나 기타 정보를 넣어주게된다.
여기서는 사용자가 로그인을 하여 성공한 경우에 userId 라는 값을 세션에 넣었다고 가정하여 보았다.
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
public class SessionConfig implements HttpSessionListener{
private static final Map<String, HttpSession> sessions = new ConcurrentHashMap<>(); //세션을 담을 객체
@Override
public void sessionCreated(HttpSessionEvent hse) {
sessions.put(hse.getSession().getId(), hse.getSession()); //getId메소드는 해당 세션의 고유 키값
}
@Override
public void sessionDestroyed(HttpSessionEvent hse) {
sessions.get(hse.getSession().getId()).invalidate();
sessions.remove(hse.getSession().getId());
}
//해당 메소드는 로그인 하는 곳에서 호출하는 메소드 이다.
public synchronized static String loginSessionChecker(String compareId){
String result = "";
for( String key : sessions.keySet() ){
HttpSession value = sessions.get(key);
if(value != null && value.getAttribute("userId") != null && value.getAttribute("userId").toString().equals(compareId) ){
result = key.toString();
}
}
removeSessionForDoubleLogin(result);
return result;
}
private static boolean removeSessionForDoubleLogin(String userId){
if(userId != null && userId.length() > 0){
sessions.get(userId).invalidate();
sessions.remove(userId);
}
return true;
}
}
loginSessionChecker라는 메소드를 만들어 주었다.
해당 메소드에서의 핵심은 세션 정보가 담긴 Map 객체를 반복문을 통해서 userId라는 값이 혹시나 들어있는지 확인하여 준 다음 만약 존재한다면 세션 고유 아이디를 가져와서 제거하여주는 부분이다.
해당 메소드를 최초 로그인을 시도하는 곳에서 호출한다면 중복된 아이디 값은 제거가 될 것 이다.
마지막으로 해당 리스너를 스프링에 등록을 하여야 하는데, 2가지 방법이 존재한다.
web.xml에 listner로 등록하거나 에노테이션으로 할 수 있다.
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
@WebListener
public class SessionConfig implements HttpSessionListener{
private static final Map<String, HttpSession> sessions = new ConcurrentHashMap<>(); //세션을 담을 객체
@Override
public void sessionCreated(HttpSessionEvent hse) {
sessions.put(hse.getSession().getId(), hse.getSession()); //getId메소드는 해당 세션의 고유 키값
}
@Override
public void sessionDestroyed(HttpSessionEvent hse) {
sessions.get(hse.getSession().getId()).invalidate();
sessions.remove(hse.getSession().getId());
}
//해당 메소드는 로그인 하는 곳에서 호출하는 메소드 이다.
public synchronized static String loginSessionChecker(String compareId){
String result = "";
for( String key : sessions.keySet() ){
HttpSession value = sessions.get(key);
if(value != null && value.getAttribute("userId") != null && value.getAttribute("userId").toString().equals(compareId) ){
result = key.toString();
}
}
removeSessionForDoubleLogin(result);
return result;
}
private static boolean removeSessionForDoubleLogin(String userId){
if(userId != null && userId.length() > 0){
sessions.get(userId).invalidate();
sessions.remove(userId);
}
return true;
}
}
마지막으로 해당 클래스를 호출하는 로그인 컨트롤러의 모습을 한번 살펴보자.
개념만 작성한 클래스의 모습이다.
@Controller
public class LoginController {
private LoginService service;
@RequestMapping(value = "/login")
public String selectLogin(@RequestParam Map<Object, Object> param, HttpSession session){
//1. 로그인 정보를 확인한다.
Map<String, String> result = service.getLoginInformation(param);
if(result != null){
//2. 만약 정보가 일치한 경우 중복로그인을 제거한다.
SessionConfig.loginSessionChecker(result.get("userId"));
//3. 그리고 새로운 로그인에 따른 해당 아이디값을 세션에 넣어준다.
session.setAttribute("userId", result.get("userId"));
//4. 로그인 성공 후 가야할 페이지
return "redirect:/board";
}
//5. 로그인 정보가 일치하지 않으면 로그인 페이지로 리다이렉트
return "redirect:/index";
}
}
크게 어렵지가 않다. 로그인이 성공하면 다시 userId값을 세션에 넣어주어야 하는 것을 잊지 말자.
물론 userId라는 키 값은 원하는데로 바꾸어 사용하면된다..^^
마찬가지로 SessionConfig 클래스에서 ip나 접속 기기여부 같은 조건을 여러개 준 다면 조금 더 다양한 중복로그인 방지 기능을 만들어 볼 수 있다.
댓글