验证同域名不同端口的会话

验证同域名不同端口的会话

步骤

  1. 启动俩后端
# 启动 nodejs 进程1作为 http 后端
> nodejs server.js 8001 abcdefghijk

# 启动 nodejs 进程2作为 http 后端
> nodejs server.js 8002 1234567890
  1. 使用同一个浏览器在同一标签页依次访问下述 url

    1. http://localhost:8001/set-session
    2. http://localhost:8001/validate-session
    3. http://localhost:8002/set-session
    4. http://localhost:8002/validate-session
    5. http://localhost:8001/validate-session # 此时会报错

说明

浏览器cookie存储原则:

  • 与协议(http/https)无关
  • 与端口无关
  • 与域名或IP有关(domain/IP)
  • 与设置的上下文path有关(/或/path)

附录

附录一

相关文档

HTTP Cookie 规范在 1990 年代设计时,基于”一个服务器/一个服务”的假设,将端口排除在 Cookie 隔离机制外,而这个设计在微服务和容器化时代暴露了其局限性,但由于必须保持向后兼容,无法直接修改标准,只能通过变通方案解决。

rfc6265.txt 在第一章引言中明确表示:

For historical reasons, cookies contain a number of security and
privacy infelicities. For example, a server can indicate that a
given cookie is intended for "secure" connections, but the Secure
attribute does not provide integrity in the presence of an active
network attacker. Similarly, cookies for a given host are shared
across all the ports on that host, even though the usual "same-origin
policy" used by web browsers isolates content retrieved via different
ports.

相关依据: Web_Storage_API

附录二

server.js

const http = require('http');
const url = require('url');
const querystring = require('querystring');

// Configuration
const PORT = process.argv[2] || 3000;
const FEATURE_VALUE = process.argv[3] || 'default_feature';

// In-memory storage for sessions (in production, use Redis or database)
const sessions = {};

// Parse cookies from request header
function parseCookies(request) {
const cookies = {};
if (request.headers.cookie) {
request.headers.cookie.split(';').forEach(cookie => {
const parts = cookie.trim().split('=');
if (parts.length === 2) {
cookies[parts[0]] = parts[1];
}
});
}
return cookies;
}

// Generate a random session ID
function generateSessionId() {
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
}

const server = http.createServer((req, res) => {
const parsedUrl = url.parse(req.url, true);
const path = parsedUrl.pathname;
const cookies = parseCookies(req);

// Set CORS headers
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Credentials', 'true');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');

if (path === '/set-session' && req.method === 'GET') {
// Set session endpoint
const sessionId = generateSessionId();

// Store the feature value with the session ID
sessions[sessionId] = FEATURE_VALUE;

// Set the session ID as a cookie
res.setHeader('Set-Cookie', [`sessionId=${sessionId}; HttpOnly; Path=/; Max-Age=3600`]);

res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
success: true,
sessionId: sessionId,
message: 'Session set with feature value'
}));
}
else if (path === '/validate-session' && req.method === 'GET') {
// Validate session endpoint
const sessionId = cookies.sessionId;

if (!sessionId) {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ valid: false, message: 'No session cookie found' }));
return;
}

const storedFeatureValue = sessions[sessionId];

if (!storedFeatureValue) {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ valid: false, message: 'Invalid session' }));
return;
}

// Check if the stored feature value matches the expected one
const isValid = storedFeatureValue === FEATURE_VALUE;

res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
valid: isValid,
message: isValid ? 'Session valid' : 'Feature value mismatch'
}));
}
else {
res.writeHead(404, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Not found' }));
}
});

server.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
console.log(`Feature value: ${FEATURE_VALUE}`);
console.log('Endpoints:');
console.log(' GET /set-session - Sets a session with the feature value');
console.log(' GET /validate-session - Validates session using cookie');
});