1장. 세션 처리 전체 흐름
DXCMS의 세션은 Secure.php(v5.2.2)의 initSession()과 startSession() 두 단계로 처리됩니다. initSession()은 세션 설정값만 지정하고 실제 시작은 startSession()이 담당합니다. 중요한 점은 모든 요청에서 세션을 시작하지 않는다는 것입니다.
1.1 세션 초기화 전체 다이어그램
모든 HTTP 요청 → index.php
↓
① STEP 2: Secure::initSession($isHttps)
├ 이미 세션 활성 상태면 → 즉시 return (중복 초기화 방지)
├ Redis 세션 핸들러 설정 (REDIS_SESSION_URL 있으면)
├ 파일 세션: data/sessions/ 경로 설정
├ gc_maxlifetime = 7200 (세션 파일 2시간 유지)
├ cookie_lifetime = 7200 (쿠키 2시간 유지)
├ cookie_httponly = 1 (JS 접근 차단)
├ cookie_secure = 1 (HTTPS만, $isHttps=true일 때)
├ cookie_samesite = Lax (PHP 7.3+, CSRF 방어)
└ use_strict_mode = 1 (PHP 7.1+, 미등록 세션 ID 거부)
↓
② 조건부 세션 시작 — $_dxNeedSession 판단
GET 요청 + 세션 쿠키 없음 + 비-AJAX?
├ YES + 세션 불필요 경로 → $_dxNeedSession = false → 세션 시작 안 함
└ NO (위 조건 미충족) → $_dxNeedSession = true → startSession()
↓
③ startSession() 실행 (필요한 경우만)
├ session_start()
└ 세션 ID 재생성 체크 (7200초 경과 + 비-AJAX)
↓
④ CSRF 토큰 선제 발급 (세션 있을 때만)
↓
⑤ Auth::getInstance() → loadSession()
├ 세션 있음 → HMAC 토큰 검증 → 로그인 상태 확정
└ 세션 없음 → tryRememberMe() → Remember Me 쿠키로 자동 로그인
2장. 세션 저장소 구조
2.1 파일 세션 (기본)
별도 설정 없으면 data/sessions/ 폴더에 세션 파일을 저장합니다. PHP 기본 /tmp 를 사용하지 않는 이유는 공유호스팅 환경에서 여러 사이트가 같은 /tmp 를 공유하면 타 사이트의 GC가 내 세션 파일을 삭제할 수 있기 때문입니다.
// 세션 파일 저장 경로
data/sessions/
└── sess_{세션ID} ← PHP 직렬화 형식으로 저장
// 세션 파일명 예시
sess_n7t7c88fsscnt86so29cfh42t3
// 파일 내용 (직렬화된 $_SESSION)
__regen|i:1746957742;dx_csrf|a:2:{s:5:"token";s:64:"abc...";s:6:"expire";i:1746990000;}
dx_user|a:2:{s:2:"id";i:1;s:5:"token";s:64:"sha256...";}_dx_heartbeat|i:1746957800;
// 파일 권한: 0700 (소유자만 읽기/쓰기)
// 공유호스팅에서 타 사이트가 세션 파일 접근 불가
2.2 Redis 세션 (선택)
REDIS_SESSION_URL 상수를 정의하고 Redis 서버가 연결 가능하면 자동으로 Redis 세션을 사용합니다. 멀티서버 환경, 고트래픽 사이트에서 권장합니다.
// data/config.php 에 추가
define('REDIS_SESSION_URL', 'tcp://127.0.0.1:6379');
// 또는 Redis ACL/비밀번호 사용 시
define('REDIS_SESSION_URL', 'tcp://127.0.0.1:6379?auth=비밀번호');
// 설정 흐름
Secure::connectRedis() // Redis 연결 시도
├ 성공: session.save_handler = redis
│ session.save_path = tcp://127.0.0.1:6379
└ 실패: data/sessions/ 파일 세션으로 자동 폴백
(Redis 오류가 발생해도 사이트는 정상 동작)
💡 Redis 세션 장점
멀티서버(로드밸런서 환경): 모든 서버가 동일 세션 공유 가능
성능: 파일 I/O 없음. 메모리 기반으로 읽기/쓰기 속도 10배 이상
자동 만료: Redis TTL로 세션 파일 GC 없이 자동 정리
data/sessions/ 폴더 불필요
2.3 $_SESSION 내부 구조
| 키 | 타입 | 설명 |
| dx_user | array | 로그인 정보. id + HMAC 토큰. Auth::getInstance() 가 읽고 씀 |
| dx_csrf | array | CSRF 토큰. token(64자 hex) + expire(Unix timestamp) |
| __regen | int | 세션 ID 재생성 기준 시각. Unix timestamp |
| _dx_heartbeat | int | 마지막 하트비트 시각. csrf_refresh API가 갱신 |
| _dx_last_active | int | 마지막 활동 시각. csrf_refresh API가 갱신 |
| dx_oauth_state | string | OAuth CSRF 방어용 state 값. 소셜 로그인 중에만 존재 |
| dx_oauth_redirect | string | 소셜 로그인 완료 후 돌아갈 URL |
| dx_flash | array | 플래시 메시지. type + message. 한 번 읽으면 삭제 |
| dx_board_error | array | 게시판 폼 오류 메시지. 리다이렉트 전후로 전달 |
3장. 세션 수명 관리
3.1 세션 수명 설정값
| 설정 | 값 및 의미 |
| gc_maxlifetime | 7200초(2시간) — 세션 파일의 최대 유효 시간. 마지막 접근 후 2시간이 지나면 GC 대상 |
| cookie_lifetime | 7200초(2시간) — PHPSESSID 쿠키 유효 시간. 브라우저를 닫아도 2시간 이내 재방문 시 세션 유지 |
| gc_probability / gc_divisor | 1/100 — 매 요청의 1% 확률로 GC 실행. 만료된 세션 파일 자동 삭제 |
| CSRF_TTL | 10800초(3시간) — CSRF 토큰 유효 시간. 세션보다 길어서 세션 갱신 중에도 유효 |
⚠️ 중요: gc_maxlifetime vs cookie_lifetime
gc_maxlifetime: 세션 "파일"이 서버에 얼마나 유지되는지
cookie_lifetime: 브라우저의 세션 "쿠키"가 얼마나 유지되는지
두 값이 모두 7200초(2시간)로 동일하게 설정되어 있습니다.
→ 2시간 동안 아무 활동이 없으면 세션 파일과 쿠키 모두 만료
→ 그러나 Remember Me 쿠키(dx_remember)가 있으면 자동 재로그인
3.2 세션 수명 연장 방법
세션 파일의 마지막 수정 시각(mtime)을 갱신하면 GC가 해당 파일을 삭제하지 않습니다. DXCMS는 두 가지 방법으로 수명을 연장합니다.
방법 1: $_SESSION 직접 쓰기 (글쓰기 페이지 하트비트)
// /api/csrf_refresh 호출 시 (90초마다 자동)
$_SESSION["_dx_heartbeat"] = time(); // 세션 파일 mtime 갱신
$_SESSION["_dx_last_active"] = time();
// $_SESSION에 값을 쓰면 session_write_close() 시 파일이 업데이트됨
// → mtime 갱신 → GC가 "아직 활성 세션"으로 판단 → 삭제 안 함
// → 사실상 세션 수명 2시간 자동 연장
방법 2: touch()로 세션 파일 mtime 직접 갱신 (이중 보호)
// /api/csrf_refresh 호출 시 추가 보호
$ssp = session_save_path();
$sf = $ssp . "/sess_" . session_id();
if (file_exists($sf)) @touch($sf); // mtime 직접 갱신
// Redis 세션 환경에서는 touch() 불필요 (Redis TTL 자동 연장)
// 파일 세션 환경에서만 실질적으로 동작
3.3 세션 GC 동작 원리
// PHP 세션 GC 동작 방식
매 요청의 1% 확률로 GC 실행
↓
data/sessions/ 폴더의 모든 sess_ 파일 순회
↓
파일 mtime + gc_maxlifetime(7200) < 현재시각?
├ YES → 파일 삭제 (세션 만료)
└ NO → 유지
// 중요: GC는 "마지막 쓰기 시각" 기준으로 판단
// $_SESSION 쓰기 또는 touch()로 mtime을 갱신하면
// GC로부터 보호됨
// 공유호스팅 주의사항
// PHP 기본 /tmp에 저장하면 타 사이트 GC가 내 세션을 삭제 가능
// DXCMS는 data/sessions/ 사이트별 폴더 사용으로 이 문제 해결
4장. 조건부 세션 시작 최적화
PHP 세션은 시작할 때 세션 파일에 잠금(file lock)을 겁니다. 잠금이 걸린 동안 같은 세션의 다른 요청은 대기합니다. DXCMS는 세션이 필요 없는 요청에서 세션을 시작하지 않아 이 병목을 제거합니다.
4.1 세션 스킵 판단 로직
// index.php — 조건부 세션 시작
$_dxNeedSession = true; // 기본값: 세션 필요
// 아래 세 조건이 모두 충족될 때만 스킵 검토
if (
$_SERVER["REQUEST_METHOD"] === "GET" && // GET 요청
empty($_COOKIE[session_name()]) && // 세션 쿠키 없음 (신규 방문자)
!isset($_SERVER["HTTP_X_REQUESTED_WITH"]) // AJAX 아님
) {
// 세션이 반드시 필요한 경로인지 확인
if (
strpos($uri, "/admin") !== false || // 관리자 (인증 필요)
strpos($uri, "/auth") !== false || // 로그인/회원가입
strpos($uri, "/view/") !== false || // 게시글 뷰 (조회수 중복 방지)
strpos($uri, "/api/") !== false || // API (로그인 확인)
strpos($uri, "/write") !== false || // 글쓰기
strpos($uri, "/edit") !== false || // 글수정
strpos($uri, "/reply") !== false // 답글
) {
// 세션 필요 → $_dxNeedSession = true 유지
} else {
$_dxNeedSession = false; // 세션 스킵
}
}
if ($_dxNeedSession) {
$secure->startSession(); // 세션 시작 (파일 잠금 발생)
}
4.2 세션 스킵 대상과 효과
| 요청 유형 | 세션 처리 |
| 신규 방문자 게시판 목록 GET | 세션 스킵 → 파일락 없음 → 동시 접속 성능 최대화 |
| 기존 방문자 게시판 목록 GET (쿠키 있음) | 세션 쿠키 있으므로 → 세션 시작 → 로그인 상태 확인 |
| 글쓰기/수정/답글 GET | 세션 필요 경로 → 세션 강제 시작 |
| 로그인/회원가입 GET | /auth/ 경로 → 세션 강제 시작 (CSRF 토큰 필요) |
| 게시글 뷰 GET | /view/ 경로 → 세션 강제 시작 (조회수 중복 방지) |
| 모든 POST/AJAX | GET이 아니므로 → 세션 항상 시작 |
| 관리자 모든 요청 | /admin/ 경로 → 세션 강제 시작 |
💡 성능 효과
트래픽이 많은 게시판 목록 페이지에서 효과가 큽니다.
신규 방문자(비로그인, 세션 쿠키 없음)가 목록을 볼 때 파일락이 없어
동시 수백 명의 요청을 병렬로 처리할 수 있습니다.
세션 파일 잠금으로 인한 503 에러 발생 가능성을 크게 줄입니다.
5장. 세션 ID 재생성 (하이재킹 방어)
세션 하이재킹 공격에서는 공격자가 탈취한 세션 ID로 피해자인 척 서버에 요청합니다. 세션 ID를 주기적으로 바꾸면 탈취된 ID가 곧 무효화됩니다.
5.1 재생성 조건
// startSession() 내부 — 재생성 판단
// 재생성 스킵 조건 (아래 중 하나라도 해당하면 스킵)
1. AJAX/API 요청
→ HTTP_X_REQUESTED_WITH 헤더 있거나
Accept에 application/json 포함
→ 이유: fetch/XHR 응답의 Set-Cookie가 브라우저에 적용되기 전에
location.reload() 가 실행되면 이전 세션 ID로 요청 → 세션 소실
2. __regen 값이 없음 (첫 세션)
→ 설정만 하고 재생성 안 함
3. 마지막 재생성 후 7200초 미경과
→ 현재 설정: 세션 수명(7200초) = 재생성 주기(7200초)
→ 사실상 세션이 살아있는 동안 재생성 1회만 발생
5.2 재생성 처리 코드
// 재생성 실행 (7200초 경과 + 비-AJAX일 때)
$__regenData = $_SESSION; // 기존 세션 데이터 백업
@session_write_close(); // Windows IIS: 파일 잠금 해제
@session_start(); // 새 세션 시작
@session_regenerate_id(false); // 새 세션 ID 발급
// false = 구 세션 파일 유지
// (구 파일 즉시 삭제 시 중간 요청 실패 방지)
$_SESSION = $__regenData; // 데이터 복원
$_SESSION["__regen"] = time(); // 재생성 시각 기록
// session_regenerate_id(false) vs session_regenerate_id(true)
// false: 구 세션 파일 유지 (안전, 권장)
// true: 구 세션 파일 즉시 삭제 (동시 요청 시 오류 위험)
6장. 세션 키 구조
6.1 세션 키 (keySession)
// Secure.php
private $keySession = 'dx_user'; // 고정값
// v5.2.2 변경 사항:
// 이전: DX_SECRET_KEY 기반 동적 키 (매 설치마다 다름)
// 현재: "dx_user" 고정값
// 고정값으로 바꾼 이유:
// 동적 키를 사용하면 CMS 업데이트 시 시크릿 키가 변경되어
// 기존 로그인된 모든 사용자가 자동 로그아웃되는 문제 발생
// → 고정값으로 업데이트 후에도 기존 세션 유지
// 로그인 시 세션 저장
$_SESSION["dx_user"] = array(
"id" => 123, // 회원 ID
"token" => "sha256hex", // HMAC-SHA256 토큰
);
6.2 CSRF 키 (keyCsrf)
// Secure.php
private $keyCsrf = 'dx_csrf'; // 고정값
// CSRF 토큰 세션 구조
$_SESSION["dx_csrf"] = array(
"token" => "64자 랜덤 hex",
"expire" => time() + 10800, // 3시간
);
// 토큰 유지 전략 (v6.2.0)
// 토큰 없거나 만료 → 새 토큰 생성
// 토큰 있으면 → expire만 갱신 (토큰 값 유지)
// → 연속 AJAX 요청에서도 동일 토큰 사용 가능
// → 하트비트로 expire 자동 갱신 → 장시간 글쓰기 중에도 유효
7장. 하트비트 — 세션 자동 갱신
글쓰기 페이지처럼 장시간 머무는 페이지에서 세션이 만료되지 않도록 90초마다 /api/csrf_refresh 를 호출합니다. 이것이 하트비트입니다.
7.1 하트비트 흐름
// dx-rescue.js (글쓰기/수정 페이지에서 자동 실행)
setInterval(heartbeat, 90 * 1000); // 90초마다
// 탭 활성화 시 즉시 실행 (탭 전환 후 복귀 시)
document.addEventListener("visibilitychange", function() {
if (document.visibilityState === "visible") heartbeat();
});
// heartbeat() 함수
POST /api/csrf_refresh
Body: _csrf={현재CSRF토큰}
// /api/csrf_refresh 처리 (v4.0.0)
① 세션 생존 확인
② $_SESSION["_dx_heartbeat"] = time() → 세션 파일 mtime 갱신
③ touch(sess_{세션ID}) → 이중 mtime 갱신
④ 로그인 확인
├ 세션 만료: session_expired=true + can_auto_login 반환
└ 정상: CSRF 토큰 expire 6시간 연장 + 새 토큰 반환
// 응답 받은 JS
├ success=true: CSRF 토큰 갱신 (meta 태그 + hidden 필드)
└ session_expired=true:
├ can_auto_login=true → /api/session_extend 자동 복구
└ can_auto_login=false → 로그인 모달 표시
7.2 세션 만료 판단 기준
| 상황 | 처리 |
| session_status() !== ACTIVE | 세션 파일 자체가 없음 → 완전 만료 → Remember Me 확인 |
| 세션은 있지만 dx_user 없음 | 로그인 세션 없음 (비로그인 상태) |
| dx_user 있지만 HMAC 불일치 | Auth::loadSession()에서 logout() 호출 → 강제 만료 |
| status=0인 회원 | DB 조회에서 status=1 조건 미충족 → 강제 만료 |
8장. Remember Me — 세션 자동 복구
세션이 만료되어도 dx_remember 쿠키가 살아있으면 자동으로 세션을 복구합니다. 두 가지 경로로 동작합니다.
8.1 경로 1: 페이지 로드 시 자동 복구 (Auth::tryRememberMe)
// 모든 요청에서 Auth::loadSession() 실행 시
// $_SESSION["dx_user"] 없으면 → tryRememberMe() 자동 호출
흐름:
1. dx_remember 쿠키 존재 확인
2. "userId:64자hex" 형식 파싱
3. remember_token 컬럼 존재 확인 (DxCache 1시간 캐시)
4. DB: SELECT * FROM dx_members WHERE id=? AND status=1
5. hash_equals(DB토큰, 쿠키토큰) 타이밍 공격 방어
6. remember_expires > 현재시각
7. 세션 복구:
$_SESSION["dx_user"] = ["id"=>ID, "token"=>HMAC]
8. 토큰 롤링 갱신:
- 새 64자 랜덤 토큰 생성
- DB UPDATE remember_token, expires(+24시간), last_login, last_ip
- Set-Cookie: dx_remember=ID:새토큰; HttpOnly; 24시간
8.2 경로 2: 글쓰기 중 세션 만료 시 자동 복구 (session_extend API)
// dx-rescue.js — 하트비트에서 session_expired 감지 시
흐름:
1. csrf_refresh 응답: session_expired=true, can_auto_login=true
2. JS: POST /api/session_extend
3. session_extend.php:
① dx_remember 쿠키 파싱
② DB 조회 + 토큰 검증 + 만료 확인
③ 세션 복구:
$_SESSION["dx_user"] = ["id"=>ID, "token"=>HMAC]
$_SESSION["_dx_heartbeat"] = time()
④ 토큰 롤링 갱신
⑤ 새 CSRF 토큰 반환
4. JS: 응답 success=true
→ meta csrf-token 갱신
→ hidden _csrf 갱신
→ "✅ 로그인이 자동으로 연장되었습니다" 토스트
→ 글쓰기 페이지 이탈 없이 그대로 계속 작성
💡 두 경로의 차이
경로 1 (tryRememberMe): 페이지를 새로고침하거나 새 페이지를 열 때 자동 처리
→ 사용자가 세션 만료를 인지하지 못함. 자연스러운 UX
경로 2 (session_extend): 글쓰기 페이지에서 세션이 만료되었을 때
→ 페이지 이탈 없이 현재 페이지에서 즉시 복구
→ 작성 중인 내용을 잃지 않음
8.3 Remember Me 쿠키 구조
// 쿠키명: dx_remember
// 값 형식: "{회원ID}:{64자 랜덤 hex}"
// 예시: "123:a1b2c3d4e5f6..."
// 설정
setcookie(
"dx_remember",
$userId . ":" . $token,
time() + 86400, // 유효기간: 24시간
"/", // 전체 경로
"", // 현재 도메인
$isHttps, // HTTPS 환경에서만 전송
true // HttpOnly: JS에서 접근 불가
);
// DB 저장 컬럼 (dx_members)
remember_token VARCHAR(128) NULL — 64자 hex 토큰
remember_expires DATETIME NULL — 만료일시 (24시간)
// 쿠키 만료 시 처리
// → DB: remember_token=NULL, remember_expires=NULL
// → 쿠키: 빈 값 + 과거 시간으로 삭제
9장. 로그아웃 — 세션 완전 정리
로그아웃은 세션, 쿠키, DB 토큰 세 곳을 모두 정리해야 완전합니다. 하나라도 빠지면 Remember Me로 즉시 재로그인됩니다.
9.1 logout() 처리
Auth::getInstance()->logout()
↓
① 메모리: $this->user = null
② 세션: unset($_SESSION["dx_user"])
※ session_destroy() 호출 안 함
→ 이유: CSRF 토큰, 플래시 메시지 등 다른 세션 데이터 보존
→ dx_user 키만 제거하여 로그인 정보만 삭제
③ Remember Me 쿠키 삭제
if (!empty($_COOKIE["dx_remember"])) {
setcookie("dx_remember", "", time()-3600, "/", "", false, true);
}
④ DB: remember_token=NULL, remember_expires=NULL
→ 쿠키가 탈취되어도 DB 토큰 없으면 자동 로그인 불가
⑤ 훅 실행
dx_run_hook("dx_after_logout", ["user" => $user])
⑥ 플래시 메시지 설정 + 홈으로 리다이렉트
dx_flash("로그아웃되었습니다.", "info")
dx_redirect(dx_base_url())
10장. 보안 위협별 방어 구조
10.1 세션 하이재킹 방어
| 방어 수단 | 작동 방식 |
| 세션 ID 재생성 | 7200초마다 새 세션 ID 발급. 탈취된 ID 무효화 |
| HMAC 토큰 검증 | join_date + secret_key 기반 토큰. 세션 변조 감지 |
| HttpOnly 쿠키 | JS에서 세션 쿠키 접근 불가. XSS로 쿠키 탈취 불가 |
| SameSite=Lax | 외부 사이트에서 세션 쿠키 자동 전송 차단 |
| use_strict_mode | 미등록 세션 ID 거부. 세션 고정 공격 방어 |
10.2 세션 고정 공격 방어
// 세션 고정 공격: 공격자가 피해자에게 미리 알고 있는 세션 ID 사용 유도
// 방어 1: use_strict_mode = 1
// → 서버에 존재하지 않는 세션 ID를 클라이언트가 보내도 새 ID 발급
// 방어 2: 로그인 성공 시 세션 ID 재생성 (Auth::login)
// → 로그인 전 세션 ID를 공격자가 알고 있어도 로그인 후 무효화
// 현재 구현: session_regenerate_id 명시적 호출은 없으나
// HMAC 토큰 체계로 세션 변조 자체를 무효화
10.3 CSRF 방어
// Cross-Site Request Forgery: 사용자 모르게 사이트에 요청 전송
// 방어 1: CSRF 토큰 검증
// → 모든 POST 요청에 _csrf 토큰 필요
// → 타 사이트에서는 토큰을 알 수 없음
// 방어 2: SameSite=Lax 쿠키
// → 외부 사이트에서 POST 폼 전송 시 세션 쿠키 미전송
// → 단, GET 요청은 쿠키 전송 (링크 클릭 UX 유지)
// 방어 3: X-Requested-With 헤더 확인 (AJAX)
// → 브라우저가 자동으로 추가하는 헤더가 아니므로
// 타 사이트에서 fetch로 위조 불가
11장. 트러블슈팅
Q1. 로그인했는데 금방 풀립니다.
원인 세 가지를 순서대로 확인하세요.
1. PHP 설정 확인: php.ini의 session.gc_maxlifetime 값이 7200 미만인지 확인. 공유호스팅에서 ini_set()이 막혀 있으면 .htaccess로 설정 필요
2. data/sessions/ 폴더 존재 및 쓰기 권한 확인. 없거나 권한이 없으면 PHP 기본 /tmp 사용 → 타 사이트 GC 간섭
3. Remember Me 쿠키 확인: dx_members.remember_token, remember_expires 컬럼 존재 여부 확인
Q2. 여러 브라우저 탭에서 세션이 충돌합니다.
세션은 브라우저 단위(PHPSESSID 쿠키)로 관리됩니다. 같은 브라우저의 여러 탭은 동일 세션을 공유합니다. 탭 A에서 로그아웃하면 탭 B도 세션이 사라집니다. 이것은 정상 동작입니다.
Q3. 로드밸런서 환경에서 세션이 사라집니다.
파일 세션은 각 서버의 로컬 디스크에 저장됩니다. 서버 A에서 로그인하면 서버 B로 요청이 가도 세션 파일이 없어 로그아웃됩니다. 해결책: Redis 세션 사용.
// data/config.php 에 추가
define('REDIS_SESSION_URL', 'tcp://공유Redis서버IP:6379');
// 모든 WAS 서버가 같은 Redis를 바라보도록 설정
Q4. 관리자만 강제 전체 로그아웃하려면?
// 방법 1: secret_key 변경
// data/config.php의 DX_SECRET_KEY 값 변경
// → HMAC 토큰 불일치 → 모든 세션 자동 무효화
// 방법 2: DB Remember Me 토큰 일괄 삭제
UPDATE dx_members SET remember_token=NULL, remember_expires=NULL;
// → 자동 로그인도 차단
// 방법 3: data/sessions/ 폴더 파일 전체 삭제
rm data/sessions/sess_*
// → 파일 세션 환경에서 즉시 전체 로그아웃
// → Redis 환경: redis-cli FLUSHDB (위험! 신중하게)
Q5. 세션 데이터가 너무 커서 성능이 떨어집니다.
$_SESSION에 대용량 데이터를 저장하지 마세요. DXCMS는 최소 데이터만 세션에 저장합니다. 회원 정보 전체를 세션에 캐싱하지 않고 요청마다 DB에서 조회합니다.
// 세션에 저장하는 것: 최소한으로
$_SESSION["dx_user"] = ["id"=>123, "token"=>"sha256..."];
// 약 100바이트
// 세션에 저장하지 않는 것: 회원 이름, 이메일, 권한 등
// → 요청마다 Auth::loadSession()이 DB에서 조회
// → 세션 파일 크기 최소화 → 파일 I/O 부담 감소