对于我正在从事的一个新的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 使用指定的令牌创建数据库。

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


当前回答

我要回答的是,当我们使用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在一个设备注销后。

其他回答

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

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时,将其持久化到数据库中,并与正在登录的帐户相关联。这意味着您可以从JWT中提取关于用户的其他信息,因此根据环境的不同,这可能是可行的,也可能是不可行的。

对于之后的每个请求,不仅要执行您所使用的框架附带的标准验证(我希望)(验证JWT是否有效),还包括用户ID或另一个令牌(需要与数据库中的令牌匹配)之类的东西。

注销时,删除cookie(如果使用),并从数据库中使JWT(字符串)无效。如果不能从客户端删除cookie,那么至少注销过程将确保令牌被销毁。

我发现这种方法,加上另一个唯一标识符(数据库中有2个持久化项,可用于前端),会话具有很强的弹性

每个用户字符串唯一,全局字符串散列在一起作为JWT的秘密部分,允许单独和全局令牌无效。最大的灵活性,代价是在请求身份验证期间数据库查找/读取。也很容易缓存,因为它们很少改变。

这里有一个例子:

HEADER:ALGORITHM & TOKEN TYPE

{
  "alg": "HS256",
  "typ": "JWT"
}
PAYLOAD:DATA

{
  "sub": "1234567890",
  "some": "data",
  "iat": 1516239022
}
VERIFY SIGNATURE

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload), 
  HMACSHA256('perUserString'+'globalString')
)

where HMACSHA256 is your local crypto sha256
  nodejs 
    import sha256 from 'crypto-js/sha256';
    sha256(message);

例如,用法参见https://jwt.io(不确定他们是否处理动态256位秘密)

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

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

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

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

以下方法可以提供两全其美的解决方案:

让“立即”表示“~1分钟”。

例:

User attempts a successful login: A. Add an "issue time" field to the token, and keep the expiry time as needed. B. Store the hash of user's password's hash or create a new field say tokenhash in the user's table. Store the tokenhash in the generated token. User accesses a url: A. If the "issue time" is in the "immediate" range, process the token normally. Don't change the "issue time". Depending upon the duration of "immediate" this is the duration one is vulnerable in. But a short duration like a minute or two shouldn't be too risky. (This is a balance between performance and security). Three is no need to hit the db here. B. If the token is not in the "immediate" range, check the tokenhash against the db. If its okay, update the "issue time" field. If not okay then don't process the request (Security is finally enforced). User changes the tokenhash to secure the account. In the "immediate" future the account is secured.

我们将数据库查询保存在“immediate”范围内。 如果在“即时”持续时间内有来自客户端的大量请求,那么这是最有益的。