지난 포스팅에서 쿠키(Cookie)를 다루어 보았다.
쿠키는 보안상의 문제가 많다. 언제든 탈취가 가능하기 때문이다. 보안의 문제가 많지만 쿠키는 상태 유지를 위해 필요한 요소이다. 안전한 쿠키 사용을 위해 서버에 세션(Session)이 도입되었다.
세션(Session)
세션의 존재 이유는 안전한 쿠키 사용에 있다.
안전하게 쿠키를 사용하려면 쿠키데이터는 아무도 예측할 수 없는 랜덤한 데이터여야 하고 탈취된 쿠키가 사용될 수 없도록 만료시간도 설정해야 한다. 이를 위해, 서버는 세션저장소와 세션을 관리할 세션매니저를 생성한다. 세션매니저는 세션저장소를 관리한다.
그림을 보자.
클라이언트가 서버에게 ID/패스워드를 전송하였다. 서버는 ID를 토대로 회원정보를 조회한다. 회원의 패스워드와 클라이언트가 전송한 패스워드가 일치하면 세션매니저에게 세션생성을 요청한다. 세션매니저는 세션ID를 랜덤한 값으로 생성하고 회원정보와 매핑하여 세션저장소에 저장한다. 그리고 쿠키를 생성한다. 쿠키에는 세션ID가 입력된다. 이렇게 생성된 쿠키는 클라이언트에게 전달된다. 쿠키를 갖게된 클라이언트는 요청시 쿠키도 같이 서버에 전달한다. 서버는 쿠키 안에 저장된 세션ID로 세션매니저에게 조회 요청을 한다. 세션매니저는 세션ID를 key값으로 하는 회원정보가 있는지 세션저장소를 조회하고, 조회에 성공하면 서버는 클라이언트의 접근을 인가한다. 만약 세션이 만료되어 세션저장소에서 세션ID와 회원정보가 제거되면 조회 되지 않으므로, 클라이언트는 더이상 접근을 할 수 없게 된다.
그럼 세션이 동작하는 과정을 간단히 코드로 구현해보자.
LoginController
@PostMapping("/login")
public String login(@Valid @ModelAttribute LoginForm form, HttpServletResponse response){
// DB에서 회원정보 조회 ( 비즈니스 로직 )
Member loginMember = loginService.login(form.getLoginId(), form.getPassword());
// [ 회원정보가 조회되지 않은 경우 ]
if(loginMember == null){
bindingResult.reject("loginFail","아이디 또는 비밀번호가 맞지 않습니다. ");
return "login/loginForm"; // 로그인 페이지로 리턴
}
// [ 회원정보 조회에 성공한 경우 ]
// 세션매니저에 세션 생성 요청
sessionManager.createSession(loginMember,response);
// 홈페이지로 리다이렉트
return "redirect:/";
}
로그인 URL로 요청이 들어오면 login 메소드가 호출된다. 비즈니스로직으로 회원정보를 DB에서 조회한다. 회원정보 조회에 성공하면 세션 매니저에게 회원정보를 넘기며 세션 생성을 요청한다.
SessionManager
@Component
public class SessionManager {
private static final String SESSION_COOKIE_NAME = "mySessionId"; // 쿠키명
private Map<String,Object> sessionStore = new ConcurrentHashMap<>(); // 세션저장소
// 세션 생성
public void createSession(Object value, HttpServletResponse response){
// 1. UUID를 이용한 랜덤 세션ID 생성
String sessionId = UUID.randomUUID().toString();
// 2. 세션저장소에 저장
sessionStore.put(sessionId,value);
// 3. 쿠키 생성
Cookie mySessionCookie = new Cookie(SESSION_COOKIE_NAME,sessionId);
response.addCookie(mySessionCookie);
}
// 세션 조회
public Object getSession(HttpServletRequest request){
// 1. Request에서 쿠키 조회
Cookie sessionCookie = findCookie(request, SESSION_COOKIE_NAME);
// 2. 세션저장소에서 회원정보 조회
if(sessionCookie == null) return null; // 쿠키가 없는 경우
return sessionStore.get(sessionCookie.getValue()); // 쿠키가 있는 경우
}
}
세션매니저의 세션 생성은 3단계를 거친다.
1) UUID를 이용하여 랜덤한 세션ID를 생성한다.
2) 세션저장소에 회원정보와 세션ID를 매핑하여 저장한다.
3) 세션ID와 세션명으로 쿠키를 생성하여 응답객체(Response)에 저장한다.
세션 조회과정도 2단계를 거친다.
1) Request 객체에서 쿠키를 추출한다.
2) 쿠키에 저장된 세션ID로 세션저장소에서 회원정보를 조회한다.
다시 세션생성으로 돌아오자.
세션이 생성되고 세션ID를 가진 쿠키가 응답객체에 저장되면 다시 LoginController로 돌아온다. 그럼 LoginController는 클라이언트를 홈페이지 경로로 리다이렉트 시킨다.
HomeController
@GetMapping("/")
public String homeLogin(HttpServletRequest request, Model model){
//세션매니저에서 세션 조회
Member member = (Member)sessionManager.getSession(request);
// [ 세션에 회원정보가 없는 경우 ]
if(member == null){
return "home"; // 일반 홈페이지 경로로 응답
}
// [ 세션에 회원정보가 있는 경우 ]
model.addAttribute("member",member);
return "loginHome"; // 로그인된 홈페이지 경로로 응답
}
리다이렉트된 경로로 요청이 들어오면 homeLogin 메소드가 호출된다. Request 객체에는 세션ID가 저장된 쿠키가 들어있다. 그러므로 세션매니저에 요청하여 세션저장소에 세션ID와 매핑되는 회원정보가 있는지 조회한다. 회원정보가 있다면 로그인된 홈페이지 경로로 클라이언트에 응답한다.
세션 관리는 웹서비스에서 정말 중요한 작업이다. 그러므로 서블릿은 세션 기능을 제공하고 있다. 다음 포스팅에서는 서블릿에서 제공하는 세션을 이용하여 로그인 과정을 구현해보겠다.
참고자료
'SPRING > Spring MVC' 카테고리의 다른 글
[SpringMVC] 서블릿 필터(Filter)란? (0) | 2023.08.31 |
---|---|
[SpringMVC] 서블릿 세션(Servlet Session) 이용하기 (0) | 2023.08.30 |
[SpringMVC] Cookie ( 쿠키 ) (0) | 2023.08.30 |
[SpringMVC] 검증(Validation)(8) - Bean Validation ( 폼객체 ) (0) | 2023.08.30 |
[SpringMVC] 검증(Validation)(7) - Bean Validation ( groups ) (0) | 2023.08.30 |