Nodejs etag session如何保存http会话

Nodejs etag session如何保存http会话

我们都知道,http的session是保存用户访问状态的,标识用户身份的重要的东西,但是大部分session都是基于浏览器的cookie,仅有很少一部分还在使用基于url的session机制。如果cookie被用户禁用,我们又不想使用安全性较差的url的session机制,是不是就没有办法了呢?最近在微博上看到一篇利用etag来作为sessionid保存http会话的文章颇有感触,当然我在此文章的基础上更深入一步,让这套etag的session架构似乎可以投入生产了。 项目代码地址:https://github.com/DoubleSpout/etagSession 大家可以从上面的github地址下载代码,运行

node app.js

然后访问127.0.0.1:3001/login/load/ 这个查看效果。

下面我们根据截图一起来领略下etag作为sessionid的妙用

1、首先我们打开地址:/login/load/ 进入登录跳转页面 图片地址

2、因为我们第一次进入,所以跳转到表单页面,文本框内没有内容 图片地址

3、填写表单内容并且提交表单 图片地址

4、我们关闭浏览器,重新打开chrome,直接输入/login/index/ 这个url地址 图片地址

5、显示出了刚才我们提交的信息了,信息已经同session一样保存在了服务端 图片地址

7、打开127.0.0.1下面的cookie信息,发现没有任何使用cookie的痕迹 图片地址

这个etag保存session的架构,我在ie6,7,8,9,10, chrome下均测试通过。

简单说一下流程吧: 1、用户进入 /login/load/进入跳转页,这个页面的关键是让用户先加载一次 /etag_session.js 这个文件,保证用户浏览器第二次再加载这个文件时会带上 if-none-match 这个请求头。

2、用户进入 /login/index/ 之后,会先加载 /etag_session.js 这个文件,然后服务端会根据用户请求的 if-none-match 响应相同的etag,然后响应这个js文件也会带上etag这个字符串。所以这个 /etag_session.js 内容是动态生成的,但是我们也要保证让浏览器每次都来请求这个js文件,所以我们的响应头得加上 ‘Cache-Control’ = ‘max-age=0’。 前端js会将 etag_session.js 返回的etag字符串保存在表单的隐藏文本域中,这样提交给nodejs之后,就将etag标识和用户的信息关联起来了。

3、用户关闭浏览器,重新打开地址之后,因为请求 /etag_session.js 这个地址的 if-none-match 的值没有变,所以动态生成的 /etag_session.js 文件的内容就可以根据用户请求的 if-none-match 的值来找到用户的信息返回出去了。前端js拿到用户信息就将信息写入文本框中

总结下吧,利用etag进行session存储优缺点都很明显: 缺点: 1、如果用户ctrl+f5,或者清空了缓存,用户就不得不重新登录了 2、万一网站被xss注入了,很容易就获取了用户的sessionId,如果基于cookie,可以使用httponly来避免js获取sessionid

优点: 1、如果cookie被禁用,etag方案似乎是比url方案更安全靠谱一些 2、如果存在多个站点需要做单点登录,合理利用etag方案可以简单不少,不用像以前我们通过url跳转刷cookie,而且碰到iframe的时候蛋疼的ie还不发cookie,需要加上p3p头。 利用etag机制之后,只需要加载一下 登录中心的 某一个js,如果用户在登录中心登录过,就可以轻松获取用户的基本信息了,但是处于安全考虑可能只会获取一些诸如头像,昵称,性别等信息。 所以如果合理的配合使用cookie和etag来保存http会话,可以让我们解决一些复杂的跨站点登录问题提供一个简单的思路

博客原文:http://snoopyxdy.blog.163.com/blog/static/6011744020137209451697/


7 回复

Nodejs etag session 如何保存 HTTP 会话

引言

HTTP 的 session 是一种用于保存用户访问状态的重要机制。然而,大多数情况下,session 都是基于浏览器的 cookie 实现的。如果用户的 cookie 被禁用,并且我们不希望使用安全性较低的 URL session 机制,是否还有其他方法来实现 session 功能呢?

最近在阅读一篇关于使用 ETag 来作为 session ID 以保存 HTTP 会话的文章后,我发现这种方法有潜力成为一个可行的解决方案。本文将介绍如何使用 ETag 作为 session ID 来保存 HTTP 会话,并展示一个简单的示例代码。

项目代码地址

你可以从 GitHub 上下载该项目的源代码:etagSession

运行该项目:

node app.js

然后访问 http://localhost:3001/login/load/ 查看效果。

流程说明

  1. 初次访问:用户访问 /login/load/ 页面,这个页面会加载 /etag_session.js 文件。这确保了当用户再次访问该页面时,浏览器会发送 if-none-match 请求头。

  2. 表单提交:用户填写表单并提交。服务器根据 if-none-match 值响应相同的 ETag,同时响应的 JS 文件也包含 ETag 字符串。这些信息会被保存在一个隐藏的文本域中,以便后续提交给 Node.js。

  3. 重新打开浏览器:用户关闭浏览器并重新打开,直接访问 /login/index/。由于 if-none-match 值没有变化,服务器可以根据该值找到之前保存的用户信息并返回给客户端。前端 JS 将这些信息填充到表单中。

示例代码

app.js

const express = require('express');
const crypto = require('crypto');

const app = express();
const sessions = {};

app.get('/login/load/', (req, res) => {
    const etag = crypto.randomBytes(16).toString('hex');
    sessions[etag] = {};
    res.setHeader('ETag', etag);
    res.send(`
        <html>
            <head>
                <script src="/etag_session.js"></script>
            </head>
            <body>
                <form action="/login/index/" method="POST">
                    <input type="text" id="username" name="username" />
                    <input type="hidden" id="etag" name="etag" value="${etag}" />
                    <button type="submit">Submit</button>
                </form>
            </body>
        </html>
    `);
});

app.post('/login/index/', (req, res) => {
    const { etag } = req.body;
    const { username } = req.body;

    sessions[etag].username = username;
    res.send(`<h1>Welcome ${username}</h1>`);
});

app.get('/etag_session.js', (req, res) => {
    const etag = req.headers['if-none-match'];
    if (sessions[etag]) {
        res.setHeader('Content-Type', 'application/javascript');
        res.send(`document.getElementById('username').value = '${sessions[etag].username}';`);
    } else {
        res.status(304).send();
    }
});

app.listen(3001, () => {
    console.log('Server running on port 3001');
});

etag_session.js

// 动态生成的脚本,用于填充表单数据
document.getElementById('username').value = 'Initial Value';

总结

使用 ETag 作为 session ID 的优点包括:

  • 如果用户的 cookie 被禁用,ETag 方案比 URL 方案更安全可靠。
  • 对于多站点的单点登录问题,ETag 方案可以简化处理。

缺点包括:

  • 用户强制刷新(Ctrl+F5)或清除缓存会导致用户需要重新登录。
  • XSS 攻击更容易获取用户的 session ID。

结合 cookie 和 ETag 可以帮助我们解决一些复杂的跨站点登录问题,提供一个简单的解决方案。


hoho~图片重见天日了

楼主的文章都很有货,想拜师啊。

如果用户ctrl+f5,或者清空了缓存,用户就不得不重新登录了 这个是硬伤……

mark下 思路不错 不过多了个proxy和一次跳转 稍微复杂了一点点

弱弱的问一句,文中提到“在ie6,7,8,9,10, chrome下均测试通过”,那firefox有没有测试过?

要在Node.js中使用ETag来保存HTTP会话,我们可以利用HTTP的缓存机制和ETag头部。下面是实现这一机制的一个简化示例,展示了如何使用ETag来保存和恢复会话信息。

示例代码

首先,我们需要创建一个简单的Express应用,其中包含处理登录、验证和渲染页面的逻辑。

const express = require('express');
const crypto = require('crypto');
const app = express();

// 模拟的用户数据
let users = {
    '12345': { username: 'testUser', email: 'test@example.com' }
};

app.use(express.json());

// 生成ETag
function generateETag(data) {
    return crypto.createHash('sha256').update(JSON.stringify(data)).digest('hex');
}

app.get('/login/load', (req, res) => {
    const etag = req.headers['if-none-match'];
    let sessionId = req.cookies.sessionId || etag;
    
    let userData = users[sessionId];
    if (!userData && !etag) {
        // 第一次访问,重定向到登录页面
        res.redirect('/login/form');
    } else {
        // 根据ETag或Cookie中的sessionId查找用户数据
        let newETag = generateETag(userData);
        if (newETag === etag) {
            // 如果ETag匹配,返回相同的内容
            res.set('ETag', newETag);
            res.send(`
                <form id="loginForm">
                    <input type="hidden" name="etag" value="${newETag}">
                    <label>Email: <input type="text" name="email" value="${userData.email}"></label>
                    <button type="submit">Submit</button>
                </form>
            `);
        } else {
            // 如果ETag不匹配,返回新的数据
            res.set('ETag', newETag);
            res.send(`
                <form id="loginForm">
                    <input type="hidden" name="etag" value="${newETag}">
                    <label>Email: <input type="text" name="email" value=""></label>
                    <button type="submit">Submit</button>
                </form>
            `);
        }
    }
});

app.post('/login', (req, res) => {
    const { email } = req.body;
    let sessionId = crypto.randomBytes(20).toString('hex');
    users[sessionId] = { email };

    res.cookie('sessionId', sessionId, { maxAge: 900000, httpOnly: true });
    res.redirect(`/login/load`);
});

app.listen(3001, () => console.log('Server running on port 3001'));

解释

  1. ETag生成generateETag函数用于根据用户数据生成一个唯一的ETag。
  2. GET请求处理:当用户访问/login/load时,服务器检查是否存在ETag(即用户是否已登录)。如果存在,则返回带有ETag的HTML表单;否则,重定向到登录表单页面。
  3. POST请求处理:当用户提交表单时,服务器创建一个新的会话ID并将其与用户数据关联起来。然后设置一个HTTP-only Cookie,确保客户端JavaScript无法访问它。
  4. 表单处理:表单中包含一个隐藏的etag字段,用于在客户端JavaScript中捕获ETag,并将其发送回服务器以保持会话状态。

这种方法允许我们在用户禁用Cookie的情况下仍然保持会话状态,但需要注意的是,ETag的安全性可能不如传统的Cookie+HTTPS组合,因此在生产环境中还需要额外的安全措施。

回到顶部