반응형
중복로그인 방지 수정기(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를 상속받아 이용하고 있었다.
우리가 해야할 부분은 중복로그인을 막는 부분을 구현해야하므로 인증 성공 후 세션에서 체크해 이미 인증된 세션이 있는 경우 해당 세션을 끊어주면된다.
코드를 확인해보니 인증 성공 후 로그인 이력을 남기는 부분과 어느 페이지로 이등시킬지에 대한 부분만 구현되어있었다.
자 그럼 절차는 다음과 같다.
- 인증성공시 사용자를 담을 세션 객체 선언
- 인증성공 후 수행되는 절차 중 마지막에 성공한 사용자를 세션 객체에 담아주기 구현
- 인증성공 후 수행되는 절차 중 앞부분에 성공한 사용자가 이미 세션에 들어있는지 찾고 있다면 해당 사용자 로그아웃
구현
// 세션객체 생성
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 |