티스토리 뷰

반응형

(참고 : Spring Boot와 IntelliJ를 기준으로 작성된 글입니다.)

 

Cookie

클라이언트(사용자)가 로그인을 하였을 때 가장 핵심은 서버와의 상태를 유지시켜야 한다는 것이다

기본적으로 비연결성을 유지하는 HTTP의 특성상, 상태를 유지시켜주기 위해서 Cookie와 Session을 사용한다

쿠키를 사용하여 연결상태를 유지시키는 과정은 대략적으로 아래와 같다

하지만 쿠키만 사용 할 시 아래와 같은 문제점이 있다

1. 클라이언트쪽에서 쿠키의 값을 변경할 수 있다 (쉬운 규칙으로 이루어져 있다면 다른 쿠키값으로 접근을 할 수 있다)

2. 쿠키에 보관된 정보를 훔쳐갈 수 있다

3. 쿠키를 한번 훔쳐가면 평생 사용할 수 있다

 

Session

위에서 살펴본 문제점들을 해결하기 위하여 Session을 사용하기 전, 어떠한 대안이 있는지 살펴보자

1. 클라이언트쪽에서 쿠키의 값을 변경할 수 있다 (쉬운 규칙으로 이루어져 있다면 다른 쿠키값으로 접근을 할 수 있다)

대안) 예측이 불가능한 랜덤 값(토큰)을 노출하고 서버에서 별도로 토큰과 사용자의 정보를 매핑해서 인식한다, 토큰은 서버에서 관리한다

 

2. 쿠키에 보관된 정보를 훔쳐갈 수 있다

대안) 중요한 데이터(신용카드, 주민번호 등등)를 쿠키에 노출되지 않도록 한다

 

3. 쿠키를 한번 훔쳐가면 평생 사용할 수 있다

대안) 쿠키가 일정시간 지나면 소멸하도록 하여 쿠키의 영구적인 접근 허용을 막는다

 

정리해보자면 중요한 정보를 서버에 저장하고, 토큰(랜덤값)을 노출시키며 서버에서 관리하고, 일정 시간이 지나면 쿠키를 갱신할 수 있도록 해야 할 것이다, 로그인이라는 개념 또한 특정 서비스에 국한되는 개념이 아니고 대부분의 서비스에서 사용이 되어야 하는 개념이기 때문에 서블릿이 공식적으로 상태 유지를 위한 세션을 지원한다.

 

HTTP Servlet Session

세션을 사용하게 되면 자바의 UUID를 사용하여 예측이 불가능한 랜던값(토큰)을 생성하여 쿠키에 전달한다

이제 클라이언트(사용자)사이드에서 노출이 되는 토큰은 아무런 규칙성이 없기 때문에 예상이 어렵다

또한 만에라도 악성 프로그램등에 노출이 되어 Session값이 탈취당한다 해도, SessionId에는 중요한 정보가 없기 때문에 안전하다

세션을 관리하기 위한 기능은 크게 3가지로 나뉜다

 

1. 세션 생성

UUID를 통한 추정이 불가능한 랜덤 값을 생성

세션 저장소에 sessionId와 보관할 값 저장

sessionId로 응답 쿠키를 생성해서 클라이언트에 전달

 

2. 세션 조회

클라이언트가 요청한 sessionId 쿠키의 값으로, 세션 저장소에 보관한 값 조회

 

3. 세션 만료

클라이언트가 요청한 sessionId 쿠키의 값으로, 세션 저장소에 보관한 sessionId와 값 제거

 

HTTP Servlet의 경우 위와 같은 기능을 간편하게 사용할 수 있도록 제공한다

예시와 함께 살펴보자

// 로그인 시
@PostMapping("/login")
public String loginV3(@Validated @ModelAttribute("loginForm") LoginForm loginForm, BindingResult bindingResult, HttpServletRequest request) {
    if (bindingResult.hasErrors()) {
        return "login/loginForm";
    }

    Member loginMember = loginService.login(loginForm.getLoginId(), loginForm.getPassword());

    if (loginMember == null) {
        bindingResult.reject("loginFail", "아이디 또는 비밀번호가 맞지 않습니다.");
        return "login/loginForm";
    }

    //로그인 성공 처리
    //세션이 있으면 세션 반환, 없으면 신규 생성
    HttpSession session = request.getSession();
    //세션에 로그인 회원 정보 보관
    session.setAttribute(SessionConst.LOGIN_MEMBER, loginMember);

    return "redirect:/";
}

위의 예시에서는 login의 데이터를 받아오는 별도의 Form객체를 만들어 로그인 데이터를 가지고 온다, 이때 request.getSession()을 사용하는데 (true) 혹은 (false) 값을 적용할 수 있다

true = 세션이 있으면 세션을 반환, 없으면 신규 생성하여 반환 (즉 세션의 값을 항상 가질 수 있게 해준다)

false = 세션이 있으면 세션을 반환, 없으면 null을 반환 (즉 세션의 값이 가지고 있는 경우에만 세션이 유지된다)

request.getSession()의 경우에는 기본값이 true이니 참고하여 사용하면 되겠다

이렇게 세션이 있는지의 검증을 거치고 나면 session.setAttribute를 사용하여 세션 저장소에 저장하게 된다

 

로그아웃의 경우 session.invalidate()를 사용하면 세션을 삭제하게 된다

// 로그아웃 시
@PostMapping("/logout")
public String logoutV3(HttpServletRequest request) {
    //세션을 삭제
    HttpSession session = request.getSession(false); 
    if (session != null) {
        session.invalidate();
    }
    return "redirect:/";
  }

이렇게 세션을 이용하면 상태를 클라이언트와 서버의 상태를 좀 더 간편하게 유지시켜 줄 수 있다

하지만 클라이언트(사용자)는 로그아웃 기능을 사용하지 않는 경우가 많다는(그냥 브라우저 종료) 문제점이 존재한다

이렇게 되면 세션을 삭제해야 되는 과정을 실행하지 못해 세션이 계속 유지된다

이럴 때 세션의 타임아웃을 설정하면 세션의 종료 시점 즉 알아서 삭제되는 시점을 설정할 수 있다

 

Session Timeout

(참고 : Spring Boot 기준으로 작성된 글입니다)

세션의 삭제 시점을 설정할 때, 만약 클라이언트(사용자)가 접속한 시점을 기준으로 삭제시점을 설정하면 매우 간편하다, 하지만 실제 서비스에서의 상황을 생각해보면 사용자가 웹서핑을 한참 하고 있다가 다시 세션 요청 즉 로그인 요청이 온다면 매우 번거로울 것이다, 이에 servlet.timeout이라는 것을 제공할 때 사용자가 마지막으로 요청한 시간을 기준으로 삭제 시점을 갱신한다

 

application.properties에 아래와 같이 작성하게 되면 글로벌 설정이 적용되어 모든 세션에 적용이 된다

각 세션은 사용자가 마지막 행위를 한 시점으로 계속 갱신되어 설정 시간 동안 아무런 요청을 하지 않으면 세션이 자동으로 삭제된다

// 글로벌 설정 - 모든 세션에 적용
server.servlet.session.timeout=60 //초단위이며, 글로벌 설정은 분단위로 설정해야 함 (60 = 1분, 120 = 2분)

만약 특정 세션에 다르게 적용하고 싶다면 setMaxInactiveInterval()를 사용하여 설정할 수 있다

// 특정 세션 단위로 시간 설정
session.setMaxInactiveInterval(1800); //1800초

세션의 종료 시점은 상황에 맞게 설정하여야 하나 주의해야 할 점들이 있다

세션의 경우 결국 메모리를 사용하기 때문에 사용자 * 세션의 데이터로 급격하게 사용량이 늘어나 서비스의 장애를 일으킬 수 있다

기본값이 30분이고 보통 30분을 사용한다고 하니 참고해서 사용하면 될 듯하다

 

@SessionAttribute

스프링에서 세션을 더욱 편리하게 조회할 수 있도록 @SessionAttribute라는 애노테이션 기반의 세션 조회 기능을 지원한다

이름에서 알 수 있듯이 session + attribute의 기능을 사용할 수 있다 (주의: 세션을 생성하는 기능은 제공하지 않는다)

// 세션의 여부에 따라서 Home 화면에 접근여부를 검증하는 로직
//@SessionAttribute 미사용 시
@GetMapping("/")
public String homeLoginV3(HttpServletRequest request, Model model) {

	//세션 있는지 검수 없으면 home으로
    HttpSession session = request.getSession(false);
    if (session == null) {
        return "home";
    }

    Member loginMember = (Member) session.getAttribute(SessionConst.LOGIN_MEMBER);

    //세션에 회원 데이터가 없으면 home
    if (loginMember == null) {
        return "home";
    }

    //세션이 유지되면 로그인으로 이동
    model.addAttribute("member", loginMember);
    return "loginHome";

}  

//@SessionAttribute 사용 시
@GetMapping("/")
public String homeLoginV3Spring(@SessionAttribute(name = SessionConst.LOGIN_MEMBER, required = false) Member loginMember, Model model) {

    //세션에 회원 데이터가 없으면 Home
    if (loginMember == null) {
        return "home";
    }

    //세션이 유지되면 로그인으로 이동
    model.addAttribute("member", loginMember);
    return "loginHome";
}

 

핵심정리

로그인을 하였을 때의 핵심은 클라이언트(사용자)와 서버의 상태를 유지시켜주어야 한다는 것이다, 하지만 비연결상태를 기본적으로 유지하는 HTTP의 특성상 상태를 유지하기 위하여 보통 쿠키를 활용하여 상태 유지를 시켜준다, 하지만 쿠키만 이용할 시에 보안이 매우 취약하다는 단점이 있다

 

이런 문제점들을 해결 주는 것이 바로 session이다, 로그인의 기능은 특정 서비스가 아닌 대부분의 서비스에서 필요한 개념인 만큼 HTTP Servlet에서 공식으로 세션 기능을 지원한다, 세션을 이용하면 랜덤 값(토근)을 쿠키에 담아 보내어 해당 값이 노출되어도 중요한 정보가 없기 때문에 보안상으로 안전하고, 세션의 삭제 시점을 설정할 수 있어 만약에 세션 값을 해킹당했다 하더라도 새로운 세션을 사용하기 때문에(설정 시간에 따라서) 하나의 세션을 해킹했다 하더라도 영구적으로 접근이 불가능하다

 

세션을 사용할 때 주의점은 결국 서버의 메모리를 사용하는 만큼 적절하게 시간을 적용하여 (너무 길지 않게) 서버의 메모리에 부담을 주지 않게끔 설계해야 한다는 것이다

 

더보기

개인 학습을 위해 작성되는 글입니다.

제가 잘못 알고 있는 점에 대한 지적 / 더 나은 방향에 대한 댓글을 환영합니다.

 

참조 링크:

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2/dashboard

 

반응형
댓글