티스토리 뷰

spring security에서는 중복 로그인 방지를 다음과 같이 간단하게 수행할 수 있다. 

 

 <security:session-management>
     <security:concurrency-control max-sessions="1" expired-url="/loginDuplicate.fo"/>
 </security:session-management>   

 

하지만 login page를 통하지 않고 SSO로 로그인을 하기를 원하는 경우에는 위와 같은 방법이 통하지 않는다. 

login page를 통해 로그인을 하는 경우라면 AuthenticationFilter (username과 password를 사용하는 form 기반 인증)를 통과하며 유저 인증 처리를 진행하는데 SSO는 이런 과정과는 다르게 직접 인증처리를 구현해야 하므로 위와 같은 concurrency-control 의 제어를 사용할 수 없는 것이다. 그럴때 직접 컨트롤을 통해 중복 로그인 처리를 해줘야 한다. 

 

중복 로그인 처리를 하기 위해서는 가장 중요한점은 현재 세션의 목록을 가지고 올수 있어야 한다. 세션의 목록은 HttpSessionAttributeListener을 통해서 만들고 관리할 수 있다. HttpSessionAttributeListener는 세션에 변화가 생겼을 때 이를 감지하고 관련된 작업을 할수 있게 해주는 역할을 해준다고 보면 된다. 

 

import java.util.concurrent.ConcurrentHashMap;

import javax.servlet.ServletConfig;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.stereotype.Component;

@Component
public class SessionAttributeListener implements HttpSessionAttributeListener {
	public static ConcurrentHashMap<HttpSession, String> sessionMap = new ConcurrentHashMap<>();

	public void init(ServletConfig config) {
	}

	@Override 
	public void attributeAdded(HttpSessionBindingEvent event) { 
		HttpSession session = event.getSession(); 
		String attributeName = event.getName(); 
		
		if(attributeName.equals("SPRING_SECURITY_CONTEXT")){ 
			if(event.getValue() instanceof SecurityContextImpl) { 
				SecurityContextImpl securityContextImpl = (SecurityContextImpl) event.getValue(); 
				Authentication authentication =securityContextImpl.getAuthentication(); 
				String userId = authentication.getName(); 
				
				if(sessionMap.get(session) == null) { 
					sessionMap.put(session, userId); 
				} 
			} 
		} 
	}
	
	@Override 
	public void attributeRemoved(HttpSessionBindingEvent event) { 
		String attributeName = event.getName(); 
		HttpSession session = event.getSession(); 
		
		if (attributeName.equals("SPRING_SECURITY_CONTEXT")) { 
			if(event.getValue() instanceof SecurityContextImpl) { 
				if(sessionMap.get(session) != null) { 
					sessionMap.remove(session); 
				}
			} 
		}
	}  
	
	@Override 
	public void attributeReplaced(HttpSessionBindingEvent event) { 
		
	} 
}

HttpSessionAttributeListener에 대해 쉽게 설명을 하자면 우리가 흔히 알고 있는 세션에 어떤 속성이 추가가 되면 attributeAdded() 이 호출이 되고 어떤 속성이 삭제가 되면 attributeRemoved(), 속성이 수정이 되면 attributeReplaced()가 호출된다고 생각하면 된다. 이것을 implements 하여 각 이벤트에 대한 처리를 해줄 수 있다.

 

우리는 spring security 를 이용하고 있으므로 세션의 속성 중 SPRING_SECURITY_CONTEXT 의 변화만 감지를 하고 있으면 되는것이다. SPRING_SECURITY_CONTEXT는 별도로 세션에 값을 넣어주지 않아도 spring security의 필터 중 SecurityContextPersistenceFilter가 실행되면서 HttpSessionSecurityContextRepository 클래스의 saveContext() 가 실행되면서 우리도 모르게 세션에 들어가 있다. SPRING_SECURITY_CONTEXT 가 생성이 되었다는건 세션은 인증된 세션이고 이 안에는 인증 관련 각종 정보가 들어있다고 생각하면 된다.

 

따라서 이것으로부터 세션의 정보(userId) 를 가져와서 그 세션과 함께 sessionMap에 담거나 지운다.  즉 이 SessionAttributeListener는 세션의 변화를 감지하여 세션을 관리하는 sessionMap에서 세션을 추가하거나 삭제하는 역할을 한다. 

 

위의 listener를 사용하려면 web.xml으로 관리하는 프로젝트인 경우에는 다음을 추가시켜주고 java config인 경우는 그냥 위처럼 @Component만 붙여서 사용할 수 있다.

<listener> 
	<listener-class>xxx.login.SessionAttributeListener</listener-class>
</listener>  

 

listener 등록까지 마쳤다면 세션이 생성되거나 삭제할때 sessionMap에서 세션 관리가 이루어지기 시작한 것이다.

이제 세션을 생성하거나 삭제를 시켜주는 부분에 대해 SSO login 과정에서 처리를 해주면 된다. 

 

private void duplicateCheck(String userId) {
	ConcurrentHashMap<HttpSession, String> sessionMap = SessionAttributeListener.sessionMap; 
	Enumeration<HttpSession> keys = sessionMap.keys(); 
		
	while (keys.hasMoreElements()) { 
		HttpSession session = (HttpSession) keys.nextElement(); 
		if(sessionMap.get(session).equals(userId)){
			session.invalidate();
		} 
	}
}

 

sessionMap을 이용하여 중복로그인을 방지하는 메소드를 적절한 곳에 넣어주고 SSO 로그인 도중 userId를 획득하는 부분에서 이 메소드를 호출해주면 된다. 

원리는 간단하다. sessionMap을 가져와서 이 map이 <session, userId> 구조로 담긴걸 이용해 여기서 추출한 userId와 SSO login 시 들어온 userId와 비교하여 같으면 이미 세션이 있다고 간주하고 기존의 session을 invalidate 시키고 그 userId로 다시 로그인을 진행하여 세션을 만든다. 세션을 invalidate 시키면 SessionAttributeListener의 attributeRemoved()가 호출이 되어 sessionMap에서 해당 세션이 사라지고 다시 세션이 생성이 되면 attributeAdded()가 호출이 되어 sessionMap에 값이 추가되는걸 볼수 있을것이다. 

댓글
최근에 올라온 글
최근에 달린 댓글
«   2024/05   »
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