[스프링] 쿠키, 세션

gyeol·2024년 1월 2일

스프링

목록 보기
32/50
post-thumbnail

김영한 님의 '스프링 MVC 2편 - 백엔드 웹 개발 핵심 기술'을 듣고 적은 글입니다.

도메인의 중요성

도메인은 화면, UI, 기술 인프라 등등의 영역은 제외한 시스템이 구현해야 하는 핵심 비즈니스 업무 영역을 말한다.
웹 기술이 바뀌더라도 도메인은 그대로 유지할 수 있어야 한다. 이렇게 하려면 웹은 도메인을 알고있지만 도메인은 웹을 모르도록 설계해야 한다. 이것을 웹은 도메인을 의존하지만, 도메인은 웹을 의존하지 않는다고 표현한다.
예를 들어, 웹 패키지를 모두 삭제해도 도메인에는 전혀 영향이 없도록 의존관계를 설계하는 것이 중요하다.
우리는 단방향 의존관계를 만들어야 한다.

쿠키

로그인의 상태를 유지하기 위해서는 쿠키를 사용해야 한다.
서버에서 로그인에 성공하면 HTTP 응답에 쿠키를 담아 브라우저에 전달하도록 한다. 이렇게 되면 브라우저는 앞으로 해당 쿠키를 지속해서 보내준다.

쿠키의 종류

  • 영속 쿠키 : 만료 날짜를 입력하면 해당 날짜까지 유지
  • 세션 쿠키 : 만료 날짜를 생략하면 브라우저 종료시까지만 유지
Cookie cookie = new Cookie(....);
response.addCookie(cookie);

로그인에 성공하면 쿠키를 생성하고 HttpServletResponse에 담는다. 쿠키 이름과 값을 지정해준다. 웹 브라우저는 종료 전까지 id를 서버에 계속 보내준다.

Cookie cookie = new Cookie(...);
cookie.setMaxAge(0);
response.addCookie(cookie);

쿠키를 소멸할 때에는 서버에서 해당 쿠키의 종료 날짜를 0으로 지정해 쿠키를 삭제해준다.

문제점

쿠키를 사용하게 되면 심각한 보안 문제가 존재한다

1. 쿠키 값은 임의로 변경할 수 있다.

  • 클라이언트가 쿠키를 강제로 변경하면 다른 사용자가 된다.
  • 웹 브라우저의 개발자 모드를 통해 쿠키 값을 바꾸면 다른 사용자의 이름이 보이는 걸 확인할 수 있다.

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

  • 쿠키 안에 개인 정보 등 중요한 정보들이 들어있다면 털릴수도 있다.
  • 이 정보가 웹 브라우저에도 보관되고, 네트워크 요청마다 계속 클라이언트에서 서버로 전달된다.

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

  • 해커가 쿠키를 훔쳐가 그 쿠키로 악의적인 요청을 시도할 수 있다.

해결방안

쿠키에 중요한 값을 노출하지 않고 사용자 별로 예측 불가능한 랜덤 값을 노출하고 서버에서 토큰과 사용자 id 를 매핑해서 인식하도록 하고 서버에서 그 토큰을 관리하도록 한다.
해커가 예상할 수 없는 랜덤 토큰을 생성하도록 하거나 해커가 토큰을 가져가도 시간이 지나면 사용할 수 없도록 만료 시간을 짧게 유지하도록 한다.

세션

쿠키는 보안 문제가 있었으므로 이 문제를 해결하기 위해서는 중요한 정보를 서버에 저장하는 것이 좋다. 그리고 클라이언트와 서버는 추정 불가능한 임의의 식별자 값으로 연결해야 한다.
이렇게 서버에 중요한 정보를 보관하고 연결을 유지하는 방법을 세션이라 한다.

세션 ID를 생성하는데 추정 불가능 해야하기에 우리는 자바에서 제공해주는 UUID를 사용하면 된다.
ex) Cookie: mySessionId=zz0101xx-bab9-4b92-9b32-dadb280f4b61

클라이언트와 서버는 결국 쿠키로 연결되어야 한다

  • 서버는 클라이언트에 mySessionId라는 이름으로 세션 ID만 쿠키에 담아 전달한다.
  • 클라이언트는 쿠키 저장소에 쿠키를 보관한다.
  • 중요한 정보는 클라이언트에게 넘기지 않고 추정 불가능한 세션 ID만 쿠키를 통해 클라이언트에 전달한다.

보안된 점

  • 쿠키 값 변경 가능 -> 예상 불가능한 복잡한 세션 ID 사용
  • 쿠키에 보관하는 정보 해킹 가능성 -> 세션 아이디가 해킹당해도 중요한 정보 없음
  • 쿠키 해킹 후 사용 -> 해커가 토큰을 털어가도 만료 시간이 짧기에 사용 못함. 해킹이 의심되는 경우 해당 세션 강제로 서버에서 제거 가능

세션 직접 개발

세션을 직접 개발해서 사용할 때에는 크게 3가지 기능만 제공해주면 된다.

1. 세션 생성

  • 임의의 추정 불가능한 랜덤 세션 아이디를 생성
  • 세션 저장소에 아이디 보관할 값 저장
  • 세션 아이디로 응답 쿠키 생성해 클라이언트에 전달

2. 세션 조회

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

3. 세션 만료

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

서블릿 HTTP 세션

서블릿은 세션을 위해 HttpSession이라는 기능을 제공한다.
서블릿을 통해 HttpSession을 생성하면 다음과 같은 쿠키를 생성한다. 쿠키 이름은 JSESSIONID이고, 값은 추정 불가능한 랜덤 값이다.
ex) Cookie: JSESSIONID=5B78E23B513F50164D6FDD8C97B0AD05

세션을 생성하려면 request.getSession(true)를 사용하면 된다.

public HttpSession getSession(boolean create);
//디폴트는 true

getSessioncreate옵션을 보면 truefalse가 존재한다.
세션이 있으면 기존 세션을 반환한다는 점은 같지만 세션이 없을 때의 처리 방식이 서로 다르다.

  • request.getSession(true) : 세션이 없으면 새로운 세션을 생성해 반환
  • request.getSession(false) : 세션이 없으면 새로운 세션을 생성하지 않고 null 반환

이때 true로 해놓을 경우, 로그인하지 않은 사용자에게도 의미 없는 세션이 만들어지기에 세션을 찾아서 사용하는 시점에는 false 옵션을 사용해 세션이 자동으로 생성되지 않도록 한다.

세션에 데이터를 보관하는 방법은 session.setAttribute(...)를 사용한다. 이를 사용하면 하나의 세션에 여러개의 값을 저장할 수 있다.

@SessionAttribute

스프링은 세션을 더 편리하게 사용할 수 있도록 @SessionAttribute를 지원한다.

이미 로그인 된 사용자를 찾을 때는

@SessionAttribute(name = "...", required = false) ...

이렇게 사용하면 된다. 이 기능은 세션을 생성하진 않는다.

TrackingModes

웹 브라우저가 쿠키를 지원하지 않을 때 쿠키 대신 URL을 통해 세션을 유지하는 방법이다. 이 방법을 사용하려면 URL에 이 값을 계속해서 포함해 전달해야 한다. 타임리프 같은 템플릿은 엔진을 통해 링크를 걸면 jsessionid를 URL에 자동으로 포함해준다. 서버 입장에서는 웹 브라우저가 쿠키를 지원하는지 하지 않는지 최초에는 판단하지 못하기에 쿠키 값도 전달하고 URL에 jsessionid도 함께 전달한다.

이 방식을 사용하고 싶지 않다면 application.properties

server.servlet.session.tracking-modes=cookie

이걸 입력해주면 된다.

세션 정보

로그를 사용해 세션 정보를 출력해 확인해볼 수 있다.

log.info("sessionId={}", session.getId());
log.info("maxInactiveInterval={}", session.getMaxInactiveInterval());
log.info("creationTime={}", new Date(session.getCreationTime()));
log.info("lastAccessedTime={}", new Date(session.getLastAccessedTime()));
log.info("isNew={}", session.isNew());
  • sessionId : 세션Id, JSESSIONID 의 값
    ex) 34B14F008AA3527C9F8ED620EFD7A4E1
  • maxInactiveInterval : 세션의 유효 시간
    ex) 1800초, (30분)
  • creationTime : 세션 생성일시
  • lastAccessedTime : 세션과 연결된 사용자가 최근에 서버에 접근한 시간
  • isNew : 새로 생성된 세션인지, 아니면 이미 과거에 만들어졌고, 클라이언트에서 서버로
    sessionId ( JSESSIONID )를 요청해서 조회된 세션인지 여부

세션 타임아웃 설정

세션은 사용자가 로그아웃을 직접 호출해 session.invalidate()가 호출되는 경우 삭제된다. 하지만 대부분 사용자들은 로그아웃하지 않고 브라우저를 종료한다. 문제는 HTTP가 비연결성이므로 서버 입장에선은 해당 사용자가 웹 브라우저를 종료한것인지 아닌지 인식하지 못한다.
따라서 서버에서 세션 데이터를 언제 삭제헤야하는지 판단하기 어렵다.

Stateless

HTTP는 무상태 프로토콜이기에 클라이언트와 서버가 요청과 응답을 주고받으면 연결이 끊어짐. 클라이언트와 서버는 서로 상태를 유지하지 않음.

문제점

  • 세션과 관련된 쿠키 해킹당했을 경우 오랜 시간이 지나도 해당 쿠키로 악의적 요청할 수 있음
  • 세션은 기본적으로 메모리에 생성되는데 메모리의 크기는 무한하지 않기에 꼭 필요한 경우만 생성해서 사용해야함

이런 문제점들이 있기에 종료 시점을 사용자가 서버에 최근 요청한 시간을 기준으로 30분 정도를 유지해주는 것이다. 이렇게 하면 사용자가 서비스를 이용하고 있으면 세션의 생존 시간이 30분으로 계속 늘어나게 된다. HttpSession은 이 방법을 사용한다.

application.properties

server.servlet.session.timeout=60

를 추가해준다. 최소 60초이며 우리가 시간을 지정해줄 수 있다.

세션의 타임아웃 시간은 해당 세션과 관련된 JSESSIONID를 전달하는 HTTP 요청이 있으면 현재 시간으로 다시 초기화 된다. 이렇게 초기화되면 세션 타임아웃으로 설정한 시간동안 세션을 추가로 사용할 수 있다.
LastAccessedTime이후로 타임아웃 시간이 지나면 WAS가 내부에서 해당 세션을 제거한다.

주의할 점은, 세션에서는 최소한의 데이터만 보관해야한다는 점이다. 보관한 데이터 용량 * 사용자 수로 세션의 메모리 사용량이 급격히 늘어나 장애로 이어질 수 있기에 적당한 시간을 선택하는 것이 필요하다.

profile
공부 기록 공간 '◡'

0개의 댓글