对于我正在从事的一个新的node.js项目,我正在考虑从基于cookie的会话方法(我的意思是,将id存储到用户浏览器中包含用户会话的键值存储中)切换到使用JSON Web Tokens (jwt)的基于令牌的会话方法(没有键值存储)。

这个项目是一个利用socket的游戏。IO——在一个会话(web和socket.io)中有多个通信通道的情况下,有一个基于令牌的会话会很有用。

如何使用jwt方法从服务器提供令牌/会话失效?

我还想了解使用这种范例应该注意哪些常见的(或不常见的)陷阱/攻击。例如,如果这种模式容易受到与基于会话存储/cookie的方法相同/不同类型的攻击。

所以,假设我有以下内容(改编自this和this):

会话存储登录:

app.get('/login', function(request, response) {
    var user = {username: request.body.username, password: request.body.password };
    // Validate somehow
    validate(user, function(isValid, profile) {
        // Create session token
        var token= createSessionToken();

        // Add to a key-value database
        KeyValueStore.add({token: {userid: profile.id, expiresInMinutes: 60}});

        // The client should save this session token in a cookie
        response.json({sessionToken: token});
    });
}

口令登录:

var jwt = require('jsonwebtoken');
app.get('/login', function(request, response) {
    var user = {username: request.body.username, password: request.body.password };
    // Validate somehow
    validate(user, function(isValid, profile) {
        var token = jwt.sign(profile, 'My Super Secret', {expiresInMinutes: 60});
        response.json({token: token});
    });
}

--

会话存储方法的注销(或失效)需要更新KeyValueStore 使用指定的令牌创建数据库。

在基于令牌的方法中似乎不存在这样的机制,因为令牌本身将包含通常存在于键值存储中的信息。


当前回答

简单地创建添加以下对象到你的用户模式:

const userSchema = new mongoose.Schema({
{
... your schema code,
destroyAnyJWTbefore: Date
}

当你在/login上收到POST请求时,将这个文档的日期更改为date .now()

最后,在您的身份验证检查代码中,即在您的中间件中检查isauthenticated或protected或任何您使用的名称,只需添加一个检查myjwt的验证。iat大于userDoc.destroyAnyJWTbefore。

如果您想在服务器端销毁JWT,那么在安全性方面,这个解决方案是最好的。 这个解决方案不再依赖于客户端,它打破了使用jwt的主要目标,即停止在服务器端存储令牌。 这取决于您的项目上下文,但最可能的是您想要从服务器上销毁JWT。

如果您只想从客户端销毁令牌,只需从浏览器中删除cookie(如果您的客户端是浏览器),在智能手机或任何其他客户端上也可以这样做。

如果选择从服务器端销毁令牌,我建议您使用Radis通过实现其他用户提到的黑名单样式来快速执行此操作。

现在的主要问题是:jwt无用吗?上帝知道。

其他回答

我一直在考虑的一种方法是在JWT中始终有一个iat(发布时间)值。然后,当用户注销时,将时间戳存储在用户记录上。在验证JWT时,只需将iat与上次注销的时间戳进行比较。如果iat更老,那么它是无效的。是的,您必须访问DB,但如果JWT在其他方面有效,我将始终提取用户记录。

我所看到的主要缺点是,如果他们在多个浏览器中,或者有一个移动客户端,它会让他们退出所有的会话。

这也是一种很好的机制,可以使系统中的所有jwt无效。部分检查可以针对最后有效的iat时间的全局时间戳。

在经过一些研究后,我的观点如下。 在登出期间,确保发生了以下事情…

清除客户端存储/会话

分别在登录或注销发生时更新用户表的上一次登录日期-时间和注销日期-时间。因此登录日期时间应该总是大于注销(或者如果当前状态是登录且尚未注销,则将注销日期保持为空)

这远比保持额外的黑名单表和定期清洗简单。多设备支持需要额外的表来保存loggedIn、注销日期和一些额外的详细信息,如操作系统或客户端详细信息。

我是这样做的:

生成一个唯一的散列,然后将其存储在redis和JWT中。这可以称为会话 我们还将存储特定JWT发出的请求数量——每次JWT被发送到服务器时,我们将请求增加整数。(这是可选的)

因此,当用户登录时,将创建一个唯一的散列,存储在redis中并注入到JWT中。

当用户试图访问受保护的端点时,您将从JWT中获取唯一的会话散列,查询redis并查看它是否匹配!

我们可以以此为基础,让我们的JWT更加安全,如下所示:

每个特定JWT发出的X请求,我们生成一个新的唯一会话,将其存储在我们的JWT中,然后将前一个会话列入黑名单。

这意味着JWT是不断变化的,并防止过时的JWT被黑客攻击、窃取或其他东西。

上面的想法很好,但是有一种非常简单的方法可以使所有现有的jwt无效,那就是简单地改变秘密。

如果您的服务器创建了JWT,用一个秘密(JWS)对其进行签名,然后将其发送给客户端,简单地更改秘密将使所有现有的令牌无效,并要求所有用户获得一个新的令牌进行身份验证,因为他们的旧令牌突然根据服务器变得无效。

它不需要对实际的令牌内容(或查找ID)进行任何修改。

显然,这仅适用于当您希望所有现有令牌过期时的紧急情况,对于每个令牌过期,需要上述解决方案之一(例如短令牌过期时间或令牌内存储的密钥无效)。

我要回答的是,当我们使用JWT时,我们是否需要提供从所有设备注销功能。这种方法将对每个请求使用数据库查找。因为即使服务器崩溃,我们也需要持久安全状态。在用户表中,我们将有两列

LastValidTime(默认为创建时间) 登录(默认值:true)

每当有来自用户的注销请求时,我们将LastValidTime更新为当前时间,login - in更新为false。如果有一个登录请求,我们不会改变LastValidTime,但登录将被设置为true。

当我们创建JWT时,我们将在有效载荷中有JWT创建时间。当我们授权一项服务时,我们将检查3个条件

JWT有效吗 JWT有效负载创建时间是否大于用户LastValidTime 用户是否已登录

让我们来看一个实际的场景。

用户X有两个设备A和b,他在晚上7点使用设备A和设备b登录到我们的服务器(假设JWT过期时间是12小时)。A和B都有JWT, createdTime: 7pm

在晚上9点,他丢失了他的设备b,他立即从设备a注销。这意味着现在我们的数据库X用户条目的LastValidTime为“ThatDate:9:00:xx:xxx”,登录为“false”。

在9:30,Mr.Thief尝试使用设备b登录,我们将检查数据库,即使登录是假的,所以我们不允许。

晚上10点,x先生从他的设备A登录,现在设备A有JWT,并创建了时间:10点。现在database Logged-In被设置为true

晚上10点半,小偷先生试图登录。即使“登录”是真的。数据库中的LastValidTime是晚上9点,但是B的JWT创建的时间是晚上7点。所以他不会被允许访问该服务。所以使用设备B没有密码,他不能使用已经创建的JWT在一个设备注销后。