[브라우저] 쿠키가 아닌 세션을 이용해보자

ChoiYongHyeun·2024년 3월 19일
0

브라우저

목록 보기
8/16
post-thumbnail

[브라우저] 쿠키란 무엇일까
[브라우저] 쿠키 도둑들의 종류

이전 글들을 통해 stateful 한 정보를 주고 받기 위한 방법으로

쿠키를 사용하는 경우에 대해 공부했고

쿠키를 사용 할 때 발생 할 수 있는 문제점들을 공부해봤다.

이번에는 쿠키를 활용한 방법인 Session 에 대해 알아보자


Session 의 사전적 의미

세션(session)은 컴퓨터 과학에서, 특히 네트워크 분야에서 반영구적이고 상호작용적인 정보 교환을 전제하는 둘 이상의 통신 장치나 컴퓨터와 사용자 간의 대화나 송수신 연결상태를 의미하는 보안적인 다이얼로그(dialogue) 및 시간대를 가리킨다. 따라서 세션은 연결상태를 유지하는 것보다 연결상태의 안정성을 더 중요시 하게 된다.
세션 - 위키백과

세션이랑 둘 이상의 통신 장치 간의 연결 상태를 의미한다.

통신 장치 간 연결이 시작되면 , 두 통신 장치는 연결에 따른 고유한 세션을 가지며

두 장치 간 연결이 종료되면 세션이 종료된다.

이후 새로운 연결이 시작되면 다른 고유한 세션을 갖는다.

Session 을 인증/인가에 사용하는 이유 : 쿠키가 갖는 단점

이번 글은 사용자의 정보를 쿠키에 저장해두는 것이 아닌, 세션을 활용하여 저장하는 방법에 대한 이야기이다.

그렇다면 왜 쿠키에 사용자 정보를 저장해두느 것이 아니라 세션을 활용하려 하는걸까 ?

쿠키는 보안상 취약

[브라우저] 쿠키 도둑들의 종류 에서 보았지만 쿠키는 브라우저 메모리 단에 저장되기 때문에

나쁜 의도를 가진 사람이 해당 사용자의 브라우저 메모리에 접근하여 쿠키를 탈취하는 것이

가능했기 때문이다.

이에 차라리 서버측에서는 정보를 저장하는 주체가 사용자가 아닌, 서버에서 정보를 저장하고

서버에서 정보를 보호하기로 마음을 먹었다.

이 때 서버에서 저장하고 있는 사용자 정보를 사용자가 이용 할 수 있도록 하게 해주는 같은 역할이 세션이다.

쿠키는 중앙 집중식 제어가 불가능

사용자의 정보를 쿠키를 이용해 저장하면 각기 다른 사용자의 브라우저 메모리 단에 저장해두기 때문에

정보들을 서버 측에서 제어하기가 어렵다.

하지만 세션을 이용하여 사용자 정보를 서버 측에서 저장하고 관리한다면 중앙 집중식 제어가 가능하다.

쿠키는 적은 용량만 사용 가능

쿠키는 브라우저 메모리 단에서 저장되기 때문에 아주 적은 용량만 사용이 가능했다.

하지만 사용자 정보를 서버에서 저장하게 되면 용량의 한계가 없기 때문에 확장성이 뛰어났다.

세션은 어떤 식으로 작동할까 ?

세션 생성과 클라이언트 식별

서버가 클라이언트와 연결을 시작 할 때

클라이언트를 식별 할 수 있는 고유한 Session ID 를 생성해 클라이언트에게 제공한다.

제공된 Session ID 는 클라이언트의 쿠키 저장소에 저장되어 서버와 통신 할 때

항상 이 고유한 Session ID 를 쿠키에 담아 서버에 제출하고

서버는 쿠키에 담긴 Session ID 를 통해 클라이언트 식별이 가능하다.

Session ID 는 고유하기 때문이다.

세션 객체 생성

나는 npmexpress-session 을 이용했기 때문에 세션 객체에 익숙하지만 다른 언어나 다른 라이브러리에서는 틀릴 수도 있다.
현재 이 글은 express-session 을 기준으로 작성되었다.

쿠키를 사용 할 때에는 클라이언트 쿠키 저장소에 민감한 개인 정보들을 저장해뒀다면

세션을 활용할 때에는 서버 메모리 or 디스크 상에 개인 정보를 저장해두고

각 고유한 Session ID 들을 이용해 서버 메모리나 디스크에 개인 정보에 접근한다.

다만 매번 Session ID 를 이용해 방대한 서버에 접근하는 것은 비효율적이기 때문에

서버는 Session Storage 를 두고 Session ID 를 통해 접근 가능한 Session Object 를 관리한다.

Session Object 의 모습은 서버의 설계자에 따라 다르지만 사용자 기본 설정 , 인증 토큰 및 기타 세션별 정보와 같은 데이터를 저장한다.

이런식으로 Session ObjectSession Storage 에 저장해두고 Session ID 를 통해

해당 Session Object 에 접근하여 Session Object 에 담긴 정보들을 이용 한다.

예를 들어 Session IDabc123 인 클라이언트가 서버에서 다른 페이지로 이동 할 때

  1. 1 page 에서 2 page 로 옮기며 서버에 요청을 보냄
  2. 이 때 클라이언트는 서버 측에 Cookie : sessionId = abcd 를 보냄
  3. 서버는 sessionId 를 통해 서버에 저장된 Session Object 에 접근하여 사용자의 theme , defaultOption 등을 확인 후 적합한 리소스 제공

다음과 같은 단계로 이동한다.

이를 통해 stateful 한 정보 교환을 Session Id , Seesion Object 를 통해 할 수 있다.

세션을 이용한 사용자 인증과 인가

위 예시에서 클라이언트의 Session ID 를 이용해서 서버의 Session Storage 에 존재하는 Session Object 에 접근하고

Session Object 에 존재하는 클라이언트의 정보를 이용해서 사용자 식별 및 인가를 한다고 했다.

세션은 사용자가 로그인을 통해 인증 을 받든, 받지 않든 서버에 접속한 모든 사용자에게 제공된다.

사용자가 인가가 필요한 리소스에 접근하기 위해 로그인을 하는 경우

사용자와 서버의 연결 상태가 변경되었기 때문에 세션이 변경된다.

이처럼 사용자가 로그인과 같은 활동을 통해 인증 을 받게 되면

새로운 세션이 시작 된 것으로 간주하여 , 기존에 발급 받았던 세션은 제거하고

인증된 Session ID 를 제공한다. 인증된 Session ID 에는 인가 받아야만 사용 가능한 정보가 담긴

Session Obejct 에 접근이 가능하다.

예를 들자면 이런 식으로 말이다.

정리

정리해보자

세션을 이용한 인증과 인가

  1. 세션이란 두 통신 장치 간의 연결 상태를 의미하는 고유한 상태를 의미한다.
  2. 고유한 세션을 통해 통신 장치 간 서로를 식별하는 것이 가능하다.
  3. 클라이언트가 인증을 통해 연결 상태가 변경되면 (인증된 상태) 새로운 세션 아이디를 발급받고 해당 세션 아이디를 통해 정보가 담긴 세션 객체에 접근 가능하다.

다만 세션 객체에 접근 시키는 주체는 클라이언트가 아닌 서버이다.

  1. 이처럼 탈취당할 수 있는 민감한 정보를 서버 저장소 단에 저장해두고 , 클라이언트에게는 서버 저장소에 접근 가능한 키 역할을 하는 Session ID 만을 제공함으로서 사용자의 정보를 좀 더 보호 할 수 있도록 한다.

실전을 통해 좀 더 살펴보기

세션 생성

app.get('/', (req, res) => {
  if (!req.session.num) req.session.num = 1;
  else req.session.num += 1;

  console.log(`${req.session.num} 번째 방문`);

  res.status(200).send('hi');
});

기본적으로 express-session 을 사용하면 해당 페이지에 접속하는 경우 자동으로 session IDSession Object 가 생성된다.

세션 인증

const express = require('express');
const cors = require('cors');
const cookieParser = require('cookie-parser');
const session = require('express-session'); // session 을 이용하기 위한 라이브러리
const findUser = require('./utils/findUser');
// 디스크 상에 세션 저장소를 저장하기 위한 라이브러리
const fileStore = require('session-file-store')(session); 


const app = express();
const PORT = 8888;

require('dotenv').config();

// 기본 설정
app.use(express.json());
app.use(cors());
app.use(cookieParser());
app.use(
  session({
    secure: false, // session 을 주고 받을 환경에 대한 설정
    secret: process.env.SECRET_KEY,
    resave: false, // 세션을 언제나 저장할지
    saveUninitialized: true, // 세션에 저장할 내역이 없더라도 처음부터 세션을 생성할지 설정
    cookie: {
      // 세션 정보를 주고 받을 쿠키 정보 설정
      httpOnly: true,
      secure: false,
      maxAge: 60000,
    },
    name: 'session-cookie', // 브라우저 메모리에 sessionId 를 저장할 프로퍼티명
    store: new fileStore(), // 세션 객체에 세션 스토어 적용
  }),
);

app.use(session({..})) 부분은 사용할 session obejct 의 프로퍼티와 메소드 등을 정의한다.

secure 을 통해 sessionID 를 주고 받을 환경이 HTTPS 환경에서만 가능 할것인지를 설정하고

secerte 을 통해 session ID 를 인코딩하고 디코딩 할 키를 설정한다.

이후 다양한 프로퍼티 등이 존재하고 cookie 에서 cookie 가 전송될 설정도 설정이 가능하다.

name 에서는 cookie 에서 session ID 가 저장될 프로퍼티 명이다.

app.post('/login', (req, res) => {
  const requestId = req.body.userId || req.params.userId;
  const requestPassword = req.body.password || req.paarams.password;
  const userFinded = findUser(requestId, requestPassword); // Form 데이터로 로그인 정보를 확인

  if (req.session.user || req.session.user.userId === requestId) {
    // 1. 만약 현재 세션에 user 프로퍼티가 있고 원하는 아이디가 저장해둔 거라면  
    res.status(200).send(JSON.stringify(req.session.user)); 
    // 저장된 user 프로퍼티를 전송
    return;
  }

  if (userFinded) { // 2. 로그인에 성공했을 경우 
    console.log(`로그인 전 세션 아이디 : ${req.sessionID}`);
    req.session.regenerate((err) => { // 새로운 세션을 생성해서 
      if (err) {
        return res.status(500).send({ message: 'Session regenerate failed' });
      }
      req.session.user = userFinded; // 새로 생성한 세션 객체에는 유저 정보를 담고
      // 해당 세션 객체에 대한 정보를 보내줌 (optional)
      res.status(200).send(JSON.stringify(userFinded)); 
      console.log(`로그인 후 세션 아이디 :${req.sessionID}`);
    });
  } else {
    res.status(400).send({ message: '아이디나 비밀번호를 다시 확인하세요' });
  }
});

해당 코드는 로그인에서 세션을 발급하는 과정을 담은 코드이다. 세션 아이디가 새롭게 생성되는 과정을 살펴보자

로그인 전 세션 아이디 : VcI44q2Uwp1PUqqZa0CRzL9vXc7MrYA4
로그인 후 세션 아이디 :i4634nsLx6-4Jlkxk0TlmXdcLqp7INIv

다음처럼 로그인 전,, 후로는 세션 아이디가 변경 되며 세션 객체는 다음처럼 생겼다.

// i4634nsLx6-4Jlkxk0TlmXdcLqp7INIv.json 
{
  "cookie": {
    "originalMaxAge": 60000,
    "expires": "2024-03-19T03:13:48.626Z",
    "secure": false,
    "httpOnly": true,
    "path": "/"
  },
  "user": {
    "userId": "user1",
    "password": "password123",
    "firstName": "dongdong",
    "lastName": "kim",
    "gender": "male",
    "age": 20,
    "avatar": "http://thumbnail.10x10.co.kr/webimage/image..."
  },
  "__lastAccess": 1710817968626
}

Session Object 는 위에서 생성된 Session ID 를 통해 접근이 가능하다.

세션 종료

app.post('/logout', (req, res) => {
  req.session.destroy((err) => {
    if (err) throw err;

    res.clearCookie('session-cookie'); // 브라우저 단에서 session cookie 제거 
    res.status(200).send({ message: '로그아웃 잘됐음' });
    console.log(`----- ${req.sessionID} 로그아웃 -----`);
  });
});

세션 종료는 Session Object 를 세션 저장소에서 제거하여 진행된다.

res.clearCookie('session-cookie') 를 통해 브라우저에 저장된 세션 쿠키 (session ID 를 담고 있는)

를 제거해줄 수 있다.

만약 제거해주지 않더라도 해당 Session ID 로 접근 가능한 Session Object 가 없으니

제거해주지 않아도 인증, 인가에는 문제가 없다.

<로그인 .. >

로그인 전 세션 아이디 : NFQHF8dnWNKwRKwuAkhx9S_PGkABgAPX
로그인 후 세션 아이디 :N5WP-mHWd6-BFDEDDytnQuesDyynUZg0

<로그아웃 .. >
----- N5WP-mHWd6-BFDEDDytnQuesDyynUZg0 로그아웃 -----

세션의 한계점과 해결법

연결과 함께 Session Id , Session Obejct 을 서버 단에 저장해둔 후

네트워크에서는 Session ID 만을 주고 받고 정보는 Session Obejct 에 접근하여 사용하는 행위를 통해

쿠키에 비해 보안적인 부분에서는 낫다는 것은 부정 할 수 없다.

하지만 이러한 세션도 몇 가지 한계점을 갖는다.

1. 세션 저장소의 메모리 소모

쿠키는 브라우저 단에서 정보를 저장하기 때문에 서버 입장에서는 다양한 정보들을 저장할 저장소에 대한 부담이 없었으나

세션을 사용 할 때에는 클라이언트 별 세션 객체를 저장할 Session Stroage 를 준비해야 하기 때문에

사용자가 늘어날 수록 저장소의 용량이 늘어나야 한다는 단점이 존재한다.

완화 전략

  1. 외부 세션 저장소
    세션을 저장 할 수 있는 외부 저장소를 이용해 웹 서버 자체의 메모리 소비 문제를 완화하는데 도움믈 줄 수 있다. (Redies , MongoDB 등)
  2. 세션 데이터 최소화
    메모리에 올려두고 빠르게 세션을 보고 싶다면 세션 객체의 용량을 최소화 하는 것도 도움이 될 수 있다. 필수 정보나 자주 조회가 필요한 정보만 메모리 상에 저장해두는 방법이 있다.
  3. 세션 만료
    특정 세션이 오랜 기간동안 조회가 안된다면 메모리 상에서 지워버리는 것도 메모리 사용량을 관리하는데 도움이 될 수 있다.

2. 세션 탈취 문제

여전히 네트워크 상에서 Cookie 를 이용해 Session ID 를 주고 받기 때문에

네트워크 상에서 Session ID 가 탈취되는 경우

다른 사용자가 서버에 탈취한 Session ID 를 이용해 제한된 리소스에 접근하는 것이 가능하다는 문제가 있다.

완화 전략

  1. HTTPS
    클라이언트와 서버 간의 통신을 암호화 시켜 공격자가 쿠키를 탈취해가는 것을 어렵게 만들자

  2. HttpOnly
    XSS 공격을 통해 스크립틴 언어로 쿠키를 탈취하려 하는 (예를 들어 document.cookie 를 호출하고 서버에 제출하는 ..) 예시를 막기 위해 쿠키를 HTTP 통신에서만 접근 가능하게 하자

  3. Session ID 재생성

세션이 탈취되더라도 SessionID 의 유효기간을 짧게 하거나 , 정기적으로 새로운 Session ID 를 생성해 준다면 탈취되더라도 금방 탈취된 Session ID 의 유효기간이 지나버리기 때문에 문제를 막을 수 있다.

  1. 토큰 유효성 검사
    세션은 주로 사용자의 IP 주소 또는 에이전트에서 유지된다. 만약 세션이 탈취된 경우 해당 세션을 다른 사용자가 사용하기 때문에 IP 주소나 에이전트가 변경된다. 이에 서버 측에서는 동일한 세션인데 다른 사용자 정보를 가진 요청이 들어오면 비정상적 요청으로 판단하고 제한 할 수 있다.

3. 로드 밸런싱과의 호환 문제

만약 서버가 단일 서버가 아닌 , 서버의 부하를 줄이기 위해

로드밸런싱을 위해 여러 서버와 각 서버 별 Session Storage 를 가지고 있다고 해보자

이 경우, A 서버에서 Session ID 를 받은 클라이언트의 경우

로드 밸런싱에 의해 다른 서버로 이동된 경우 A 서버에서 받은 Session ID 를 사용하는 것이 불가능하다.

완화 전략

모든 서버에서 접근 가능한 세션 저장소를 사용하거나 SessionID 를 기본으로 고정 세션을 지원하도록 로드 밸런서를 구현해야 한다.


오케이 굿

이제 JWT 가보자구

profile
빨리 가는 유일한 방법은 제대로 가는 것이다

0개의 댓글