本文目录导读:

- 方案一:基于Cookie的通用跨域(适用于主域名相同)
- 方案二:完全不同的顶级域名(如 example.com 与 other.com)
- 方案三:基于Token的共享(推荐用于完全跨域)
- 方案四:iframe + postMessage(特殊场景)
- 总结选择建议
为PHP项目配置Session跨域,通常涉及两个核心问题:跨域请求携带Session ID(如Cookie跨域)和跨域共享Session数据(如不同子域名共享登录态)。
由于HTTP协议限制和浏览器安全策略,Session跨域有几种主流解决方案,根据项目架构选择合适方案:
基于Cookie的通用跨域(适用于主域名相同)
场景:a.example.com 和 b.example.com 共享Session。
设置顶级域名Cookie
在PHP中设置Session时,指定Cookie域为.example.com:
// 在所有应用入口统一设置
session_set_cookie_params([
'lifetime' => 0,
'path' => '/',
'domain' => '.example.com', // 注意前缀点号
'secure' => false, // HTTPS时设为true
'httponly' => true,
'samesite' => 'Lax' // 需要Lax或None(HTTPS必须为None)
]);
session_start();
配置php.ini
修改 session.cookie_domain:
session.cookie_domain = ".example.com"
共享Session存储
不同子域名必须使用同一个Session存储后端(默认文件存储会失败,因为文件路径不同),推荐使用:
- Redis 或 Memcached:最快的共享方案
- 数据库:更易控制
使用Redis示例:
// 共享的Session配置(建议用框架或统一初始化文件)
ini_set('session.save_handler', 'redis');
ini_set('session.save_path', 'tcp://127.0.0.1:6379?prefix=PREFIX_');
// 或使用PHP代码
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
session_set_save_handler(
function($savePath, $sessionName) { return true; },
function() { return true; },
function($sessionId) {
global $redis;
$data = $redis->get("SESSION:$sessionId");
return $data ?: '';
},
function($sessionId, $data) {
global $redis;
$redis->setex("SESSION:$sessionId", 3600, $data);
return true;
},
function($sessionId) {
global $redis;
$redis->del("SESSION:$sessionId");
return true;
},
function($maxlifetime) {
// GC由Redis自动处理
return true;
}
);
session_start();
注意:如果不共享存储,即使Cookie跨域成功,不同服务器/域名的Session数据也不同。
完全不同的顶级域名(如 example.com 与 other.com)
浏览器默认禁止跨域Cookie,需要:
- SameSite=None + Secure(必须HTTPS)
- CORS 配置(Access-Control-Allow-Credentials)
PHP响应头示例:
header('Access-Control-Allow-Origin: https://other.com');
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Allow-Headers: Content-Type');
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
// 处理OPTIONS预检请求
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit;
}
// Session配置
session_set_cookie_params([
'lifetime' => 0,
'path' => '/',
'domain' => '.example.com', // 这里无法解决不同顶级域名
'secure' => true, // 必须
'httponly' => true,
'samesite' => 'None' // 必须
]);
session_start();
注意:不同顶级域名无法通过Cookie本身完成共享,需要以下替代方案。
基于Token的共享(推荐用于完全跨域)
不使用传统PHP Session,改用JWT或自定义Token。
登录成功后生成Token
function generateToken($userId) {
$payload = [
'user_id' => $userId,
'exp' => time() + 3600,
'iat' => time()
];
// 使用JWT库(如 firebase/php-jwt)
$token = JWT::encode($payload, 'your-secret-key', 'HS256');
return $token;
}
前端存储Token(localStorage或Cookie)
// 登录成功后
localStorage.setItem('auth_token', token);
// 或设置Cookie(SameSite=None + Secure)
// 每次请求携带
fetch('https://api.example.com/data', {
headers: {
'Authorization': 'Bearer ' + token
},
credentials: 'include' // 如果使用Cookie方式
});
PHP验证Token
function validateToken() {
$headers = getallheaders();
$token = $headers['Authorization'] ?? '';
$token = str_replace('Bearer ', '', $token);
try {
$payload = JWT::decode($token, 'your-secret-key', ['HS256']);
return $payload->user_id;
} catch (Exception $e) {
http_response_code(401);
exit('Unauthorized');
}
}
iframe + postMessage(特殊场景)
如果A域页面通过iframe嵌入B域页面,可以利用postMessage传递Session ID:
B域(提供API):
// B域监听postMessage
window.addEventListener('message', function(event) {
if (event.origin !== 'https://a.com') return;
// 收到A域发来的Session ID
var sessionId = event.data;
// 使用该Session ID请求数据
fetch('/api/data', {
headers: {'X-Session-ID': sessionId}
});
});
总结选择建议
| 场景 | 推荐方案 | 复杂度 |
|---|---|---|
| 同主域不同子域 | Cookie + 共享Session存储 | 低 |
| 不同主域(内网/可控环境) | SameSite=None + CORS | 中 |
| 完全跨域(生产环境) | Token/JWT认证 | 中 |
| 需要维护传统Session | 反向代理统一域名(如Nginx) | 高 |
最推荐:现代项目中尽量使用Token认证替代Session,天然跨域友好,如果必须用Session,优先确保共享存储(Redis)和Cookie域正确配置。