회원가입 | 고객센터 |
DESIGNONEX
dxcms.kr
로그인 회원가입
고객센터
11. 인증 / 로그인 시스템

인증 흐름

D DX
2026.05.10 16:10(수정됨) 112 0

1장. 전체 요청 처리 흐름

사용자가 브라우저에서 URL을 입력하면 index.php가 모든 요청을 받아 순서대로 처리합니다. 인증과 보안은 STEP 2에서 완전히 초기화되며, 이후 라우팅과 컨텐츠 렌더링이 진행됩니다.


1.1 index.php STEP 순서

GET /free/write  (또는 POST, API 등 모든 요청)
  ↓
── STEP 1: 상수 정의 ─────────────────────────────────────
  DX_CMS, DX_ROOT, DX_CORE, DX_DATA, DX_VERSION 등 정의
  DX_START = microtime(true)  (성능 측정 기준)

── STEP 2: 보안 초기화 ───────────────────────────────────
  Secure::getInstance()
  ├ initSecretKeys(DX_SECRET_KEY)   세션/CSRF 키 설정
  ├ initSession($isHttps)           세션 설정값 지정
  │   ├ save_path: data/sessions/   타 사이트 GC 간섭 방지
  │   ├ gc_maxlifetime: 7200        세션 파일 2시간 유지
  │   ├ cookie_lifetime: 7200       브라우저 재시작 후에도 쿠키 유지
  │   ├ cookie_httponly: 1          JS 접근 차단 (XSS 방어)
  │   ├ cookie_secure: 1 (HTTPS)    HTTPS 전용 쿠키
  │   └ cookie_samesite: Lax        CSRF 방어
  ├ startSession()                  세션 시작
  │   └ 10분 주기 세션ID 재생성     (AJAX 요청 시 스킵)
  └ sendSecurityHeaders()           보안 헤더 발행

── STEP 3: DB 연결 + 설정 로드 ──────────────────────────
  require data/config.php
  → $db->connect(host, name, user, pass, charset, prefix)
  → dx_config 배열 로드 (DxCache 300초 캐시)

── STEP 3-B: 시크릿 키 주입 ─────────────────────────────
  Secure::initSecretKeys(DX_SECRET_KEY)
  → 세션 키: "dx_user"   (고정 — 업데이트 후 기존 세션 유지)
  → CSRF 키: "dx_csrf"   (고정)

── STEP 4: 플러그인 + 테마 + Auth 초기화 ────────────────
  load_plugins()           plugins/ 폴더 자동 로드
  DxSite::getInstance()    멀티사이트 설정
  DxTheme::getInstance()   테마 확정
  Auth::getInstance()      세션 검증 + Remember Me 자동 로그인
  DxContainer, DxExtend 초기화

── STEP 5: 라우팅 + 디스패치 ────────────────────────────
  DxExtend::runTop()       extend/top/ 실행
  DxRouter::dispatch()     커스텀 라우트 우선 처리
  Dispatcher::dispatch()   파일 기반 라우팅
  DxExtend::runBottom()    extend/bottom/ 실행
  ob_end_flush()           출력 버퍼 최종 flush


1.2 세션 최적화 — GET 요청 스킵

세션 파일 잠금(file lock)으로 인한 성능 저하를 방지하기 위해 불필요한 세션 시작을 건너뜁니다.
// 세션 시작이 필요 없는 조건
// → GET 요청 + 세션 쿠키 없음 + AJAX 아님

// 세션 필요한 경로 (강제 시작)
/admin/*     관리자 인증 필요
/auth/*      로그인/회원가입 처리
/view/*      조회수 중복 방지
/api/*       로그인 여부 확인
/write, /edit, /reply   글쓰기

// 그 외 GET 요청 + 세션 쿠키 없음
→ $_dxNeedSession = false → 세션 시작 안 함
→ 파일 락 없음 → 동시 접속 성능 향상


2장. 세션 시스템


2.1 세션 설정값 상세

설정 값 및 설명
session.save_path data/sessions/ — 공유호스팅에서 타 사이트 GC 간섭 방지. Redis 환경은 Redis 우선
session.gc_maxlifetime 7200초 (2시간) — 세션 파일 유지 시간
session.cookie_lifetime 7200초 (2시간) — 브라우저 재시작 후에도 쿠키 유지
session.use_only_cookies 1 — URL 기반 세션 ID 전달 차단
session.cookie_httponly 1 — JS에서 세션 쿠키 접근 불가 (XSS 방어)
session.cookie_secure 1 (HTTPS 환경만) — HTTPS 연결에서만 쿠키 전송
session.cookie_samesite Lax (PHP 7.3+) — CSRF 방어. 외부 사이트 링크 클릭은 허용
session.use_strict_mode 1 (PHP 7.1+) — 미등록 세션 ID 거부 (세션 고정 공격 방어)
session.gc_probability / gc_divisor 1/100 — 1% 확률로 GC 실행
session.cache_limiter nocache — 브라우저 캐시 방지 헤더 자동 발행


2.2 세션 ID 재생성 (하이재킹 방어)

// startSession() 내부 — 세션 ID 재생성 로직

// AJAX/API 요청이면 재생성 스킵
// (fetch 응답 Set-Cookie 적용 전 reload → 세션 소실 방지)
$_isAjaxReq = !empty($_SERVER["HTTP_X_REQUESTED_WITH"])
    || strpos($_SERVER["HTTP_ACCEPT"], "application/json") !== false;

// 세션 나이 7200초(2시간) 초과 시 재생성
if (!$_isAjaxReq && isset($_SESSION["__regen"])) {
    if (time() - $_SESSION["__regen"] > 7200) {
        $data = $_SESSION;           // 기존 데이터 보존
        session_write_close();       // Windows IIS 파일 잠금 해제
        session_start();
        session_regenerate_id(false); // 구 세션 파일 유지 (안전)
        $_SESSION = $data;           // 데이터 복원
        $_SESSION["__regen"] = time();
    }
}


2.3 Redis 세션 지원

REDIS_SESSION_URL 상수가 정의되어 있고 Redis 연결이 성공하면 파일 세션 대신 Redis 세션을 사용합니다. 대용량 트래픽, 멀티서버 환경에서 세션 공유가 가능합니다.
// data/config.php 또는 별도 설정 파일에서
define('REDIS_SESSION_URL', 'tcp://127.0.0.1:6379');

// 연결 성공 시: session.save_handler = redis
// 연결 실패 시: data/sessions/ 파일 세션으로 자동 폴백
// Redis 오류가 발생해도 CMS는 정상 동작 (graceful fallback)


3장. CSRF(크로스 사이트 요청 위조) 보호


3.1 CSRF 토큰 구조

// 세션에 저장되는 CSRF 토큰 구조
$_SESSION["dx_csrf"] = array(
    "token"  => "64자 랜덤 hex",  // randomHex(64)
    "expire" => time() + 10800,   // 3시간 유효
);

// v6.2.0 토큰 유지 전략:
// - 토큰이 없거나 완전 만료된 경우에만 새로 생성
// - 토큰이 있으면 expire만 갱신 (토큰 값 유지)
// → 연속 요청에서도 동일 토큰 사용 가능
// → 글쓰기 중 하트비트(90초)로 expire 자동 갱신


3.2 CSRF 토큰 발급 흐름

// 1. 페이지 로드 시 토큰 발급 (index.php STEP 2)
if ($_dxNeedSession) {
    Secure::getInstance()->csrfToken(); // 세션에 저장
}

// 2. HTML 폼에 hidden 필드로 삽입
<?php echo dx_csrf_field(); ?>
// → <input type="hidden" name="_csrf" value="abc123...">

// 3. <meta> 태그로 JS에서 접근 가능하게 출력 (layout/main.php)
<meta name="csrf-token" content="<?php echo dx_csrf_token(); ?>">

// 4. AJAX 요청 시 헤더로 전달
fetch("/api/comment", {
    headers: { "X-CSRF-Token": document.querySelector('meta[name="csrf-token"]').content }
})


3.3 CSRF 검증 흐름 (csrfCheck)

// POST 요청 처리 시 반드시 호출
dx_csrf_check(); // = Secure::getInstance()->csrfCheck()

// 검증 로직
// 1. $_POST["_csrf"] 또는 HTTP_X_CSRF_TOKEN 헤더에서 토큰 읽기
// 2. $_SESSION["dx_csrf"]["token"]과 hash_equals() 비교
//    → 타이밍 공격 방어 (일정 시간 비교)
// 3. expire 확인 (3시간 초과 시 실패)

// 검증 실패 시 처리 방식
// ① AJAX 요청: JSON 403 응답
//    {"success":false, "message":"세션이 만료되었습니다."}

// ② 로그인/회원가입 페이지:
//    토큰 제거 → GET redirect → 새 토큰으로 폼 재렌더링
//    (새로고침과 동일 효과 — 사용자 경험 보호)

// ③ 일반 POST 페이지:
//    HTTP 403 + 세션 만료 모달 HTML 출력
//    "로그인 연장" 버튼 → 팝업 로그인
//    "로그아웃" 버튼 → /auth/logout

💡 CSRF 토큰 vs 세션 만료
CSRF 토큰 만료(3시간) ≠ 세션 만료(2시간)
세션이 살아있어도 CSRF 토큰이 만료될 수 있습니다.
글쓰기 중 하트비트(/api/csrf_refresh)가 90초마다 expire를 갱신합니다.
→ 장시간 글쓰기 중에도 CSRF 토큰이 만료되지 않습니다.


4장. 보안 헤더 (sendSecurityHeaders)

모든 응답에 자동으로 발행되는 HTTP 보안 헤더입니다. Apache/Nginx .htaccess 설정이 없는 저가형 호스팅에서도 동작합니다.
 
헤더 값 및 역할
X-Frame-Options SAMEORIGIN — 클릭재킹 방어. 동일 도메인 iframe만 허용
X-Content-Type-Options nosniff — MIME 타입 스니핑 방어. 선언된 MIME 외 실행 차단
Referrer-Policy strict-origin-when-cross-origin — 외부 사이트에 경로 정보 미전송
X-XSS-Protection 1; mode=block — 구형 브라우저 XSS 필터 활성화
Permissions-Policy camera=(), microphone=(), geolocation=() — 카메라/마이크/위치 접근 차단
Strict-Transport-Security max-age=31536000; includeSubDomains (HTTPS만) — 1년간 HTTPS 강제
Content-Security-Policy script-src 'self' 'unsafe-inline' https: — XSS 방어. CDN/소켓 허용
X-Powered-By 제거 (header_remove) — PHP 버전 노출 차단


4.1 CSP (Content-Security-Policy) 상세

default-src 'self';
script-src  'self' 'unsafe-inline' 'unsafe-eval' https: blob:;
           → CDN(jQuery, FontAwesome 등) + 인라인 스크립트 허용
style-src   'self' 'unsafe-inline' https: data:;
img-src     'self' data: https: blob:;
font-src    'self' data: https:;  → Google Fonts, CDN 폰트 허용
connect-src 'self' https: wss: ws:;  → WebSocket 소켓 허용
media-src   'self' blob: https:;
frame-src   'self' https:;  → 외부 iframe(소셜 로그인 팝업 등) 허용
worker-src  'self' blob:;
object-src  'none';  → Flash 등 플러그인 완전 차단


5장. WAF (웹 애플리케이션 방화벽)

wafCheck()는 SQL 인젝션, XSS, 디렉토리 탐색, 명령어 인젝션 패턴을 GET 파라미터와 POST 본문에서 탐지합니다. 게시글 content 등 에디터 본문 필드는 검사에서 제외됩니다.


5.1 WAF 검사 대상

// 검사 대상
$_GET  → 전체 파라미터 검사
$_POST → content, body, description 등 본문 필드 제외 후 검사
         (에디터 내용의 HTML 태그가 오탐되는 것을 방지)
$_COOKIE → 세션 쿠키 값 오탐 방지를 위해 제외

// 탐지 패턴
SQL 인젝션: UNION SELECT, information_schema, INTO OUTFILE,
            sleep(), benchmark()
XSS:        <script>, blocked:
LFI:        ../../, /etc/passwd, php://, file://
명령어:     ; cat, || whoami, && id 등


5.2 WAF 차단 처리

// 탐지 시 처리 흐름
1. secLog("WAF", "Blocked: sql | URI: /write")  → 로그 기록
2. Redis에 IP 차단 등록 (30분)
   Redis::setex("dx:ban:{IP}", 1800, "WAF_SQL")
3. HTTP 403 + "<h1>403 Forbidden</h1>" 출력 후 exit

// Redis 없는 환경
→ 로그만 기록, IP 차단 없음 (파일 기반 Rate Limit으로 보완)


6장. Rate Limiting (요청 속도 제한)

동일 IP에서 짧은 시간 내에 과다 요청이 오는 경우 자동으로 차단합니다. Redis가 있으면 Redis 기반으로, 없으면 파일 기반으로 동작합니다.


6.1 기본 설정

설정 기본값 및 설명
rateWindow 10초 — 측정 윈도우
rateLimit 60회 — 10초 내 최대 요청 수
ipRateLimit 200회 — IP별 1분 최대 요청 수


6.2 사용 방법

// API 엔드포인트에서 Rate Limit 적용
$secure = Secure::getInstance();

// 로그인 시도 제한 (30초에 5회)
if (!$secure->rateLimit("login", 5, 30)) {
    dx_json(array("success"=>false, "message"=>"잠시 후 다시 시도해주세요."), 429);
}

// 댓글 작성 제한 (60초에 10회, IP별 30회)
if (!$secure->rateLimit("comment", 10, 60, 30)) {
    dx_json(array("success"=>false, "message"=>"잠시 후 다시 시도해주세요."), 429);
}

// Redis 기반 (슬롯 방식)
// → 10초 윈도우를 슬롯(floor(time()/window))으로 분할
// → Redis 오류 시 요청 허용 (가용성 우선)

// 파일 기반 폴백
// → data/cache/rl_{md5}.tmp 파일로 카운터 관리


7장. IP 처리 및 Cloudflare 지원


7.1 clientIp() 동작

// Cloudflare, 리버스 프록시 환경에서 실제 클라이언트 IP 추출

// 1. REMOTE_ADDR이 신뢰 프록시 범위인지 확인
// 2. Cloudflare: HTTP_CF_CONNECTING_IP 헤더 우선 사용
// 3. 일반 프록시: HTTP_X_FORWARDED_FOR 첫 번째 IP
// 4. 그 외: REMOTE_ADDR 직접 사용

// 신뢰 프록시 범위 (하드코딩)
127.0.0.1/32, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
+ Cloudflare IP 범위 14개
  173.245.48.0/20, 103.21.244.0/22, 103.22.200.0/22 ...


7.2 IP 차단 구조

// Redis 기반 IP 차단
Redis::get("dx:ban:{IP}")  → 값 있으면 HTTP 403 즉시 종료

// 차단 등록 (WAF 탐지 시)
Redis::setex("dx:ban:{IP}", 1800, "WAF_SQL")  // 30분

// 차단 해제 방법
// 1. 30분 자동 만료
// 2. 관리자 → Redis에서 직접 삭제
//    Redis::del("dx:ban:{IP}")


8장. 봇 탐지 (detectSuspiciousBot)

검색엔진 봇은 허용하고 스크래퍼, 자동화 도구는 로그에 기록합니다. 차단하지 않고 로그만 기록하는 이유는 정상 검색엔진 봇이 차단될 위험을 방지하기 위해서입니다.


8.1 허용 봇 화이트리스트

Googlebot, Yeti(네이버), bingbot, DuckDuckBot,
Baiduspider, kakaotalk-scrap, facebookexternalhit,
Twitterbot, LinkedInBot, AhrefsBot, SemrushBot

→ 위 봇은 탐지 로직 스킵 (정상 크롤링 허용)


8.2 의심 봇 탐지 패턴

// User-Agent가 비어있으면 경고 로그
// 아래 패턴 감지 시 로그 기록 (차단 아님)
curl, wget, python, scrapy, headless, selenium, phantomjs

// 로그 위치: data/error.log
// [WARN] Suspicious UA: python-requests/2.28.0


9장. Auth 인증 흐름 — 모든 요청마다 실행

Auth::getInstance()는 STEP 4에서 호출되며 세션을 검증하거나 Remember Me 쿠키로 자동 로그인을 시도합니다. 싱글턴이므로 한 번만 실행됩니다.


9.1 loadSession() 흐름

Auth::getInstance() 호출
  ↓
세션에 로그인 데이터 있음?
  ├ 없음 → tryRememberMe()  (Remember Me 쿠키 확인)
  └ 있음 →
      userId = $_SESSION["dx_user"]["id"]
        ↓
      DB 조회: dx_members WHERE id=? AND status=1
        ├ 없음 → logout()  (탈퇴/차단 계정)
        └ 있음 →
            HMAC 토큰 검증
            hash_hmac("sha256", "id|join_date", secret_key)
              ├ 불일치 → logout()  (세션 변조 감지)
              └ 일치 → this->user = $user (로그인 상태 확정)
                       unset($user["password"])  (비밀번호 메모리 제거)


9.2 Remember Me 자동 로그인 흐름

tryRememberMe()
  ↓
dx_remember 쿠키 존재?
  ├ 없음 → return (비로그인 상태 유지)
  └ 있음 →
      "userId:64자hex" 파싱
        ↓
      remember_token 컬럼 존재 확인 (DxCache 1시간 캐시)
        ↓
      DB 조회: WHERE id=? AND status=1
        ↓
      hash_equals(stored_token, cookie_token)  타이밍 공격 방어
        ↓
      만료 확인 (remember_expires > now)
        ↓
      세션 복구: $_SESSION["dx_user"] = [id, HMAC토큰]
        ↓
      토큰 롤링 갱신
      → 새 64자 랜덤 토큰 생성
      → DB UPDATE: remember_token=새토큰, expires=+24시간
      → Set-Cookie: dx_remember=userId:새토큰; HttpOnly


9.3 로그인 성공 시 세션 저장 구조

// 로그인 성공 후 세션에 저장
$_SESSION["dx_user"] = array(
    "id"    => 123,
    "token" => hash_hmac("sha256", "123|2025-01-01 00:00:00", $secret)
              // join_date(불변)로 생성 → IP/UA 변경에도 유지
);

// 메모리에는 비밀번호 없는 회원 정보 저장
Auth::$user = array(
    "id", "login_id", "name", "email", "role",
    "level", "point", "exp", "profile_img",
    "last_login", "last_ip" ... // password 제외
);


10장. 라우팅 흐름


10.1 라우팅 우선순위

// 1. DxRouter (커스텀 라우트)
//    → 플러그인/테마에서 DxRouter::get("/my-url", callback) 으로 등록
//    → 매칭되면 callback 실행 후 종료

// 2. Dispatcher (파일 기반 라우팅)
//    → Router::resolve()로 URL 파싱
//    → TYPE_BOARD: boards/handler.php 실행
//    → TYPE_AUTH:  core/auth/{action}.php 실행
//    → TYPE_API:   core/api/{action}.php 실행
//    → TYPE_ADMIN: admin/{action}/index.php 실행


10.2 URL 파싱 패턴

URL 예시 파싱 결과
/free/list TYPE_BOARD | board=free | action=list | id=0
/free/view/123 TYPE_BOARD | board=free | action=view | id=123
/free/write TYPE_BOARD | board=free | action=write | id=0
/free/edit/123 TYPE_BOARD | board=free | action=edit | id=123
/auth/login TYPE_AUTH | action=login → core/auth/login.php
/auth/logout TYPE_AUTH | action=logout → core/auth/logout.php
/api/comment TYPE_API | action=comment → core/api/comment.php
/admin/posts TYPE_ADMIN | action=posts → admin/posts/index.php


10.3 게시판 접근 권한 체크

// Dispatcher::checkBoardAccess()

// 읽기 액션 (list, view, search)
→ board.read_level 확인
  0: 비회원 허용
  1: 회원만 (비로그인 → 로그인 페이지로)
  9: 관리자만

// 쓰기 액션 (write, edit, delete, reply)
→ board.write_level 확인
  1: 회원만
  9: 관리자만

// 권한 없음 → dispatchForbidden() → HTTP 403


11장. extend/ 폴더 훅 실행 시점

extend/ 폴더의 PHP 파일은 요청 처리 중 세 시점에 자동으로 실행됩니다. 파일명 숫자 순서대로 실행되며 CMS 코드를 수정하지 않고 기능을 확장할 수 있습니다.
 
폴더 실행 시점 주요 용도
extend/top/ STEP 4 완료 직후 (라우팅 전) 유지보수 모드, IP 차단, 전역 리다이렉트
extend/middle/ 라우트 확정 후, 핸들러 실행 전 방문자 추적, 접속 로그, 미들웨어
extend/bottom/ 렌더링 완료 후 (ob_end_flush 전) GC, 비동기 작업, 성능 로그

💡 extend 활용 예시
extend/top/01_maintenance.php → 유지보수 시간 체크 후 공지 페이지로 이동
extend/middle/01_visit_tracker.php → 페이지별 방문자 수 집계
extend/bottom/01_session_rescue_gc.php → 만료된 rescue 레코드 자동 삭제 (2% 확률)
파일명 앞 숫자(01_, 02_)로 실행 순서 제어


12장. 암호화 유틸리티


12.1 제공 함수

함수 설명
Secure::randomBytes($length) PHP 7+: random_bytes() / PHP 5.6: openssl_random_pseudo_bytes() 폴백
Secure::randomHex($length) bin2hex(randomBytes) — 64자 hex 토큰 생성에 사용
Secure::hashEquals($a, $b) hash_equals() — 타이밍 공격 방지 문자열 비교
Secure::bcryptHash($password) password_hash(BCRYPT) — 회원가입/비밀번호 변경
Secure::bcryptVerify($pw, $hash) password_verify() — 로그인 비밀번호 검증
Secure::sanitize($value) htmlspecialchars + 비인쇄 문자 제거
Secure::esc($value) htmlspecialchars ENT_QUOTES — HTML 출력 이스케이프
Secure::safeUrl($url) 허용된 스킴(http/https/mailto)만 통과, blocked: 차단


13장. 인증 관련 전역 헬퍼 함수


13.1 CSRF 헬퍼

// CSRF 토큰 문자열 반환
dx_csrf_token()  → Secure::getInstance()->csrfToken()

// hidden input 필드 HTML 반환
dx_csrf_field()  → '<input type="hidden" name="_csrf" value="...">' 

// POST 요청 CSRF 검증 (실패 시 403 또는 redirect)
dx_csrf_check()  → Secure::getInstance()->csrfCheck()


13.2 인증 헬퍼

dx_is_login()  → Auth::getInstance()->isLoggedIn()  // bool
dx_is_admin()  → Auth::getInstance()->isAdmin()     // bool
dx_user()      → Auth::getInstance()->user()        // 회원 배열 또는 null
dx_ip()        → Secure::getInstance()->clientIp()  // 실제 클라이언트 IP


13.3 응답 헬퍼

// JSON 응답 (Content-Type 자동 설정)
dx_json(array("success"=>true), 200)

// 리다이렉트
dx_redirect(dx_base_url("auth/login"))

// 오류 페이지
dx_error("접근 권한이 없습니다.", 403)

// 설정값 읽기 (dx_config 배열)
dx_config("site_name", "DesignOneX CMS")
 

댓글0

로그인 후 댓글을 작성할 수 있습니다.
3.10 모듈 로딩 구조 플러그인 / 확장 로딩 방식 2026.04.21 3.9 공통 함수 / 유틸 재사용 방식 2026.04.21 3.9 공통 함수 / 유틸 공통 클래스 구조 2026.04.21 3.9 공통 함수 / 유틸 전역 함수 구조 2026.04.21 3.8 Extend 구조 실제 적용 흐름 2026.04.21 3.8 Extend 구조 Extend 개념 2026.04.21 3.7 Hook 시스템 Hook 시스템 활용 사례 2026.04.21 3.7 Hook 시스템 실행 타이밍 2026.04.21 3.7 Hook 시스템 Hook 개념 2026.04.21 3.6 데이터 처리 구조 공통 함수 활용 2026.04.21 3.6 데이터 처리 구조 데이터 흐름 상세 기술 2026.04.21 3.6 데이터 처리 구조 DB 접근 방식 2026.04.21 3.5 컨트롤러 구조 컨트롤러 구조 • 데이터 전달 • 실행 방식 • 역할 2026.04.21 3.4 라우팅 시스템 URL 처리 방식 • 라우팅 규칙 • 동적 라이팅 2026.04.21 3.3 실행 흐름 초기 로딩 과정 및 공통 초기화 흐름 2026.04.21 3.2 폴더 구조 install/ — 설치 및 마이그레이션 2026.04.21 3.2 폴더 구조 pages/ — 커스텀 페이지 2026.04.21 3.2 폴더 구조 data/ — 런타임 데이터 2026.04.21 3.2 폴더 구조 extend/ — 코드 자동 삽입 2026.04.21 3.2 폴더 구조 routes/ + controllers/ — 라라벨 스타일 라우팅 2026.04.21
31
전체 회원
503
전체 게시글
770
전체 댓글
442
오늘 방문
33,173
전체 방문
3
현재 접속
인기글 7일 이내
최신글
최신댓글
목록