티스토리 뷰
Spring Secruity 를 사용했을 때 세션 만료시간이 다가와 사용하지 못하는 경우가 있습니다. 그런 경우 세션 값에 저장된 내용을 꺼내 써야할 때 NullPointerException 이 종종 출력되곤 합니다.
그래서 검색을 해본 결과 Spring Security 내에서 (Java 로직 내에서) 세션이 만료됨을 체크해주는 로직은 찾기 어려웠습니다. 저는 웹 페이지를 개발하고 있었고, 해결책을 찾아보다가 결국 헤더에 넣은 자바스크립트에서 주기적으로 세션의 유효한 값을 체크하여 커스텀한 Exception 을 던져주는 것으로 방법을 찾았습니다.
원인 : 로그인된 사용자가 자신도 모르게 세션이 만료되어 로그아웃된 사용자 취급을 받는 것을 해결
1. SpringSecurity 뜯어보기
시큐리티에서는 기본적으로 아래와 설정되어 있었고, 이 부분 중 loginPage 부분을 유심히 찾아보게 되었습니다.
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(new AdminLoginFilter(authenticationManager(), userPrincipalRepository), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new AdminLoginCheckFilter(authenticationManager(), userPrincipalRepository), BasicAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/login").permitAll()
... 허용된 antMatcher 들
.hasAnyRole("ADMIN")
... 허용된 antMatcher 들
.hasAnyRole("USER")
... 허용된 antMatcher 들
.and()
.formLogin()
.loginProcessingUrl("/login")
.usernameParameter("username")
.passwordParameter("password")
.loginPage("/login") // << 이 부분
.permitAll()
.and()
.logout()
.addLogoutHandler(new CustomLogoutHandler(userPrincipalRepository))
.deleteCookies(SESSION_NAME)
;
}
loginPage 는 세션이 만료되었을 때 자동으로 리다이렉팅해주어 넘어가는 페이지를 표시하는 곳입니다. 로그인 페이지로 기본적으로 되어 있고, 이 부분을 수정하면 로그인하지 않은 사용자들이 모두 해당 페이지로 넘어가버리기 때문에 로그인 페이지에 알럿이나 메시지를 띄워 사용자들이 매번 로그인페이지에 들어갈 때마다 해당 내용을 보는 것을 선호하지 않았습니다.
CustomLogoutHandler 클래스 내에는 세션을 날려주는 역할 밖에 있지 않았고, 그래서 다른 방법을 찾아보았습니다.
2. IndexController
제가 IndexController에 추가한 요청은 2가지 입니다. 가장 먼저 화면에 진입하였을 때 커스텀한 Exception 을 throw 하는 요청 1개와, 세션을 체크하는 로직 1개를 추가하였습니다.
@GetMapping("/expired")
public String expiredPage() {
throw new ApiSessionExpiredException();
}
@GetMapping("/check-session")
@ResponseBody
public ResponseEntity<String> checkSession(@AuthenticationPrincipal UserPrincipal loginAccount) {
if(loginAccount == null) {
return ResponseEntity.badRequest().build();
}
return ResponseEntity.ok().build();
}
참고한 문서는 아래와 같습니다.
Spring security auto redirect to login page after session timeout
I need to redirect automatically to login page after session timeout or at least show alert that session is expired, I tried to configure Spring Security, but it is not working ,debugger don't catc...
stackoverflow.com
3. ApiSessionExpiredException , ControllerAdvice
ApiSessionExpiredException 이라는 커스텀 익셉션을 만들었습니다. 다른 Exception 을 사용하는 것보다 특정 기능에 작동하기 위한 클래스르 만들어서 ControllerAdvice 로 훅을 넘기는 것이 편할 것 같았습니다.
@ResponseStatus(code = HttpStatus.REQUEST_TIMEOUT)
public class ApiSessionExpiredException extends HttpClientErrorException {
public ApiSessionExpiredException() {
super(HttpStatus.REQUEST_TIMEOUT, "세션이 만료되었습니다.");
}
}
ControllerAdvice 에는 아래와 같은 내용을 추가하였습니다. 전역으로 작동하는 AOP의 내용으로 Exception 이 발동되면 필수적으로 거치게 됩니다.
/**
* 세션 만료 캐치
* @param e
* @return
*/
@ExceptionHandler(ApiSessionExpiredException.class)
public ModelAndView handleApiSessionExpiredException(ApiSessionExpiredException e) {
return new ModelAndView("expired");
}
4. header.html , expired.html
마지막으로 클라이언트에서 들어가야할 부분으로 2. Controller에서 만들어놓은 세션 확인용 API 를 호출하는 자바스크립트를 작성해야 합니다. 이 기능은 전체 페이지에 들어가는 header.html 에 작성하였고, Spring Framework 특성상 fragment 로 조절할 수 있습니다.
<!-- 세션 체크용 스크립트, 1분마다 체크 -->
<script>
let count = 0;
let uri = window.location.pathname;
setInterval(() => {
AjaxUtil.getCall("/check-session")
.done(() => {
count++;
// console.log(count);
})
.fail(() => {
console.log(uri);
if(uri != '/login') {
location.href = '/expired';
}
});
}, 60_000);
</script>
1분마다 세션이 유효한지 체크하는 로직을 작성하였고, 로그인 페이지에서 또 호출할 수 없기 때문에 uri != '/login' 인 경우만 expired.html 로 사용자를 이동시키게 됩니다.
expired.html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>만료 페이지</title>
</head>
<body>
</body>
<script>
alert("로그인 후 사용 시간이 많이 지났습니다. 다시 로그인해주세요.");
location.href = '/logout';
</script>
</html>
해당 페이지에서는 기본적으로 사용자가 시간이 만료되어 로그아웃되었다는 알럿 메시지를 보여주고, logout 페이지로 이동시켜 Redis 에 남아있는 session value 값을 invalidate() 시키는 것으로 마무리 하였습니다.
이렇게 해서 서로 핑퐁을 하는 것이 가능하도록 수정하였습니다. 도움 되시길 바라겠습니다 :)
'Server' 카테고리의 다른 글
[Jenkins] 배포 자동화 알림 Slack 으로 전송하기 (0) | 2023.02.06 |
---|---|
[Java] Java 14 Record, Entity Class로 사용 가능할까? (0) | 2023.02.02 |
[Java] 함수형 인터페이스에 대해 알아보기 (0) | 2023.02.01 |
[Java] Serialization 직렬화 분석 및 확장하기 (2023-01 수정) (1) | 2023.01.30 |
[Java] 보일러플레이트 코드와 실제 적용 후기 (0) | 2023.01.29 |
[Refactor] 자바 코드 리팩터링하기 - 3부 (0) | 2023.01.27 |