본문 바로가기

[SPRING]/Security

중복로그인(동시접속) 방지 수정기(security, AuthenticationSuccessHandler)

반응형

중복로그인 방지 수정기(security, AuthenticationSuccessHandler)

보안 관련 수정을 진행하며 관리자 사이트에 중복로그인을 막아달라는 요청이있었다.
스프링에서 다들 많이 이용하는 spring security를 이용하는 서비스였고 security 설정 파일에 설정 추가해주는것으로 끝날 줄 알았다.

security 설정 파일에 설정 추가

보통 security-context.xml 또는 자신이 설정한 설정파일에 설정을 추가해주면된다.
이 프로젝트의 경우에는 application-context.xml안에 설정이 되어있었다.
어느 파일에 설정을 하던 xml파일의 상단에

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:security="http://www.springframework.org/schema/security"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:task="http://www.springframework.org/schema/task"
        xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-5.3.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd">

스키마 추가하고 불러오기 때문에 자신의 프로젝트에 맞는 부분을 찾으면된다.

설정을 읽어보는데 이미 security에 중복로그인 방지 설정이 되어있었다.

<security:http auto-config="true" use-expressions="true" entry-point-ref="ajaxAuthenticationEntryPoint">
...
    <security:session-management invalid-session-url="/login">
        <security:concurrency-control max-sessions="1" expired-url="/login" />
    </security:session-management>
...
</security:http>

응? 설정되어있는데 왜 중복로그인이 가능하지? 이런 상황이면 로그인 프로세스를 뜯어봐야한다...ㅠㅠ

로그인 프로세스 뜯어보기

인증 - 인가 세션 방식으로 되어있고 인증관련해 AuthenticationSuccessHandler를 상속받아 이용하고 있었다.
우리가 해야할 부분은 중복로그인을 막는 부분을 구현해야하므로 인증 성공 후 세션에서 체크해 이미 인증된 세션이 있는 경우 해당 세션을 끊어주면된다.
코드를 확인해보니 인증 성공 후 로그인 이력을 남기는 부분과 어느 페이지로 이등시킬지에 대한 부분만 구현되어있었다.

자 그럼 절차는 다음과 같다.

  1. 인증성공시 사용자를 담을 세션 객체 선언
  2. 인증성공 후 수행되는 절차 중 마지막에 성공한 사용자를 세션 객체에 담아주기 구현
  3. 인증성공 후 수행되는 절차 중 앞부분에 성공한 사용자가 이미 세션에 들어있는지 찾고 있다면 해당 사용자 로그아웃

구현

// 세션객체 생성
private static final ConcurrentHashMap<String, HttpSession> activeSessions = new ConcurrentHashMap<>();

@Override
public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse res, Authentication authentication) throws IOException, ServletException {
    LoginUserDto userDto = (LoginUserDto) authentication.getPrincipal();
    RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
    String ip = req.getHeader("X-FORWARDED-FOR");
    if (ip == null) {
        ip = req.getRemoteAddr();
    }

    // 사용자가 세션에 있는지 확인
    if (checkUserAlreadyLoggedIn(userDto.getUsername())) {
        // 세션에 있는 기존 사용자 끊기
        invalidatePreviousSessions(req, res, userDto.getUsername());
    }

    // 로그인 성공한 사용자의 세션을 세션 목록에 추가
    activeSessions.put(userDto.getUsername(), session);

    // 기존 기능들 ...
    // 로그인 기록 삽입 ...
    // 적절한 페이지로 리다이렉트 ...
}

// 사용자가 이미 로그인되어 있는지 확인
private boolean checkUserAlreadyLoggedIn(String username) {
    for (String loggedInUser : activeSessions.keySet()) {
        if (loggedInUser.equals(username)) {
            return true;
        }
    }
    return false;
}

// 기존 세션 무효화
private void invalidatePreviousSessions(HttpServletRequest req, HttpServletResponse res, String username) {
        // 현재 사용자의 모든 세션 무효화
        Iterator<Map.Entry<String, HttpSession>> iterator = activeSessions.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, HttpSession> entry = iterator.next();
            if (entry.getKey().equals(username) && !entry.getValue().getId().equals(req.getSession().getId())) {
                HttpSession session = entry.getValue();
                try {
                    session.invalidate();
                    logger.info("기존 세션 로그아웃: " + username);
                } catch (IllegalStateException e) {
                    // 세션이 이미 무효화된 경우에 대한 예외 처리
                    logger.error("세션 무효화 중 예외 발생: " + e.getMessage());
                }
                iterator.remove();
                break; // 루프 종료
            }
        }
    }
반응형

'[SPRING] > Security' 카테고리의 다른 글

Spring Security5 OAuth2  (0) 2022.06.07
The type WebSecurityConfigurerAdapter is deprecated  (4) 2022.06.05