[Spring Security] Session Management

spring_security

세션 관리 #

서버는 기본적으로 사용자를 보면서 판단할 수 없다. 서버는 로그인을 통해 요청을 보낸 사용자를 구분한다. 하지만 모든 요청에 아이디/패스워드를 물어볼 수는 없는 노릇이다.

그래서 토큰을 발급하고, 세션에는 토큰을 저장해놓고 세션이 유지되는 동안, 혹은 remember-me 토큰이 있다면, 해당 토큰이 살아있는 동안은 로그인 없이 해당 토큰 만으로 사용자를 인증하고 요청을 처리해 준다.

그래서 악의적으로 정보를 취하고자 하는 해커는 세션을 탈취하기 위한 시도를 한다. 따라서 세션 관리에 헛점이 없도록 구성의 기본 내용을 잘 알아야한다.

ConcurrentSessionFilter #

ConcurrentSessionFilter

SessionManagementFilter #

http
  .sessionManagement(session->session
      .sessionFixation(fix->fix.changeSessionId())
      .maximumSessions(1) // 한 유저당 한 세션만 허용
      .key("remember-me")
      .tokenValiditySeconds(30*24*60*60)
      .maxSessionsPreventsLogin(false) // 새로들어온 세션은 인정 구 세션은 expire
      .expiredUrl("/session-expired") // expired session 사용자가 이동할 url
  )

SessionManagementFilter

.sessionManagement(s -> s
  .sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
  ...
)

세션 관리 실습 #

(본 코드는 GitHub 페이지에서 확인할 수 있습니다.)

Session Monitoring Page #

세션을 모니터링 할 수 있는 페이지를 만든다.

package com.sp.fc.web.controller;
...
@Controller
public class SessionController {

    @Autowired
    private SessionRegistry sessionRegistry
    @GetMapping("/sessions")
    public String sessions(Model model) { // (1)
        model.addAttribute("sessionList", // (3)
            sessionRegistry.getAllPrincipals().stream().map(p -> UserSession.builder() // (2)
                .username(((SpUser)p).getUsername())
                .sessions(sessionRegistry.getAllSessions(p, false).stream().map(s ->
                    SessionInfo.builder()
                        .sessionId(s.getSessionId())
                        .time(s.getLastRequest())
                        .build())
                    .collect(Collectors.toList()))
        .build()).collect(Collectors.toList()));
        return "/sessionList";
    }
    @PostMapping("/session/expire")
    public String expireSession(@RequestParam String sessionId) {
        SessionInformation sessionInformation = sessionRegistry.getSessionInformation(sessionId);
        if (!sessionInformation.isExpired()) {
            sessionInformation.expireNow();
        }
        return "redirect:/sessions";
    }
    @GetMapping("/session-expired")
    public String sessionExpired() {
        return "/sessionExpired";
    }
}

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserSession {
    private String username;
    private List<SessionInfo> sessions;
    // 세션의 사이즈를 return 해줄 getCount 메서드
    public int getCount() {
        return sessions.size();
    }
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SessionInfo {
    private String sessionId;
    private Date time;
}