初始化
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
// 应用目录为当前目录
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
|
||||
// 禁止 CDN 和浏览器缓存动态页面(确保修改即时生效)
|
||||
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
|
||||
header('Pragma: no-cache');
|
||||
ini_set('default_charset', 'UTF-8');
|
||||
|
||||
// ===== Session 安全配置(必须在 session_start 之前) =====
|
||||
ini_set('session.cookie_httponly', '1');
|
||||
// 关闭 strict_mode — strict_mode=1 会导致:
|
||||
// 浏览器带着一个已销毁的 Session Cookie 来时,PHP 静默创建新 Session,
|
||||
// 但补发的 Set-Cookie 只能在下次请求生效,造成当前请求 Session 为空
|
||||
ini_set('session.use_strict_mode', '0');
|
||||
ini_set('session.use_only_cookies', '1');
|
||||
|
||||
// P2-8 安全加固:会话 Cookie 统一使用 SameSite=Lax(降低 CSRF 风险)
|
||||
// 说明:原 SameSite=None 仅用于跨站 iframe 场景;若前台工位确需嵌入第三方站点的 iframe,
|
||||
// 可在此改回 'None',但这会重新引入 CSRF 风险,请谨慎评估。
|
||||
$isHttps = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
|
||||
|| (!empty($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == 443);
|
||||
if ($isHttps) {
|
||||
ini_set('session.cookie_secure', '1');
|
||||
}
|
||||
ini_set('session.cookie_samesite', 'Lax');
|
||||
|
||||
// ===== 自动修复 session 存储目录(LNMP 重装后目录可能丢失) =====
|
||||
$sessionPath = ini_get('session.save_path');
|
||||
if ($sessionPath && !is_dir($sessionPath)) {
|
||||
// php.ini 指定的 session 目录不存在,尝试创建
|
||||
@mkdir($sessionPath, 0777, true);
|
||||
}
|
||||
// 如果创建失败或路径为空,回退到系统临时目录
|
||||
if (!$sessionPath || !is_dir($sessionPath) || !is_writable($sessionPath)) {
|
||||
$fallbackPath = sys_get_temp_dir() . '/php_sessions';
|
||||
if (!is_dir($fallbackPath)) {
|
||||
@mkdir($fallbackPath, 0777, true);
|
||||
}
|
||||
if (is_dir($fallbackPath) && is_writable($fallbackPath)) {
|
||||
ini_set('session.save_path', $fallbackPath);
|
||||
session_save_path($fallbackPath);
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 处理浏览器带来的旧 Session Cookie(避免 Session 文件不存在导致数据丢失) =====
|
||||
$cookieSid = $_COOKIE[session_name()] ?? null;
|
||||
if ($cookieSid) {
|
||||
// 检查对应的 session 文件是否存在
|
||||
$sessionFile = ini_get('session.save_path') . '/sess_' . $cookieSid;
|
||||
if (!file_exists($sessionFile)) {
|
||||
// Session 文件已被销毁(如被 session_minimal.php 清除),
|
||||
// 浏览器还带着旧 Cookie。先设置 session_id 让它复用旧 ID,
|
||||
// session_start() 会创建新文件(strict_mode=0 允许)
|
||||
session_id($cookieSid);
|
||||
}
|
||||
}
|
||||
|
||||
session_start();
|
||||
|
||||
// ===== 补发 Session Cookie(兼容某些浏览器 Cookie 丢失场景) =====
|
||||
// P2-11 安全加固:仅在「请求已携带会话 Cookie 且当前 Session ID 发生变化」
|
||||
// (如登录/会话重生成)时补发,避免每个响应都重复发送 Set-Cookie。
|
||||
// 全新会话由 session_start() 自动发送,此处不再补发,消除重复头。
|
||||
$hadIncomingSid = isset($_COOKIE[session_name()]);
|
||||
if ($hadIncomingSid && (!isset($_COOKIE[session_name()]) || $_COOKIE[session_name()] !== session_id())) {
|
||||
$cookieParams = session_get_cookie_params();
|
||||
setcookie(
|
||||
session_name(),
|
||||
session_id(),
|
||||
[
|
||||
'expires' => 0, // 会话 Cookie
|
||||
'path' => $cookieParams['path'] ?: '/',
|
||||
'domain' => $cookieParams['domain'] ?: '',
|
||||
'secure' => $cookieParams['secure'],
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// ===== Session 诊断日志(仅首次请求记录,避免日志泛滥) =====
|
||||
if (empty($_SESSION['_diag_logged'])) {
|
||||
$_SESSION['_diag_logged'] = 1;
|
||||
$diagPath = ini_get('session.save_path');
|
||||
$diagWritable = is_writable($diagPath) ? 'writable' : 'NOT_WRITABLE';
|
||||
$diagSameSite = ini_get('session.cookie_samesite');
|
||||
$diagSecure = ini_get('session.cookie_secure');
|
||||
$diagHttpOnly = ini_get('session.cookie_httponly');
|
||||
$sessHash = session_id() ? hash('sha256', session_id()) : 'none';
|
||||
error_log("[SESSION_DIAG] path={$diagPath} {$diagWritable} samesite={$diagSameSite} secure={$diagSecure} httponly={$diagHttpOnly} session_hash={$sessHash}");
|
||||
}
|
||||
|
||||
define('APP_PATH', __DIR__ . '/');
|
||||
|
||||
// 基础URL路径(自动检测子目录,无需手动配置)
|
||||
// 例如:部署在 /mes/ 下则自动为 /mes,部署在根目录则为空
|
||||
$scriptDir = dirname($_SERVER['SCRIPT_NAME']);
|
||||
define('BASE_URL', ($scriptDir === '/' || $scriptDir === '\\') ? '' : $scriptDir);
|
||||
|
||||
// 从 .env 读取调试模式开关(生产环境设为 false)
|
||||
$envDebug = getenv('APP_DEBUG');
|
||||
if ($envDebug === false || $envDebug === '') {
|
||||
// 环境变量未设置时,从 .env 文件读取
|
||||
$envFile = __DIR__ . '/.env';
|
||||
if (file_exists($envFile)) {
|
||||
$lines = file($envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
foreach ($lines as $line) {
|
||||
$line = trim($line);
|
||||
if ($line === '' || $line[0] === '#') continue;
|
||||
if (strpos($line, '=') !== false) {
|
||||
list($k, $v) = explode('=', $line, 2);
|
||||
if (trim($k) === 'APP_DEBUG') {
|
||||
$envDebug = trim($v);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
define('APP_DEBUG', ($envDebug === 'true' || $envDebug === '1'));
|
||||
|
||||
// 启用输出缓冲,防止 PHP 错误/警告污染 JSON/HTML 输出
|
||||
ob_start();
|
||||
|
||||
// 加载全局辅助函数(必须在框架和视图之前加载)
|
||||
require(APP_PATH . 'core/helper.php');
|
||||
|
||||
// 加载框架文件
|
||||
require(APP_PATH . 'core/Core.php');
|
||||
|
||||
// 加载配置文件
|
||||
$config = require(APP_PATH . 'config/config.php');
|
||||
|
||||
// ===== v3-P2 CSP:基于 nonce 的内容安全策略(严格模式) =====
|
||||
// 每次请求生成独立 nonce 并注入 CSP 头;视图通过 csp_nonce() 为 <script>/<style> 打标记。
|
||||
// 全站内联事件处理器(onclick 等)已改造为 addEventListener / 事件委托,故 script-src 不再保留 'unsafe-inline'。
|
||||
// 第三方库均以外链 <script src> 加载(受 'self' 允许),不依赖内联脚本。
|
||||
$cspNonce = base64_encode(random_bytes(16));
|
||||
$GLOBALS['csp_nonce'] = $cspNonce;
|
||||
$cspHeader = "default-src 'self'; "
|
||||
. "script-src 'self' 'nonce-{$cspNonce}'; "
|
||||
. "style-src 'self' 'unsafe-inline'; "
|
||||
. "img-src 'self' data: blob:; "
|
||||
. "font-src 'self'; "
|
||||
. "connect-src 'self'; "
|
||||
. "frame-ancestors 'self'; "
|
||||
. "base-uri 'self'; "
|
||||
. "form-action 'self'";
|
||||
header("Content-Security-Policy: {$cspHeader}");
|
||||
|
||||
// 实例化框架类
|
||||
(new core\Core($config))->run();
|
||||
Reference in New Issue
Block a user