对于我正在从事的一个新的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无用吗?上帝知道。

其他回答

即使从存储中删除令牌,它仍然有效,但只是在短时间内有效,以降低它被恶意使用的可能性。

您可以创建一个拒绝列表,一旦从存储中删除令牌,就可以将令牌添加到该列表中。如果你有一个微服务,所有其他使用这个令牌的服务都必须添加额外的逻辑来检查这个列表。这将集中您的身份验证,因为每个服务器都必须检查一个集中的数据结构。

If you are using axios or a similar promise-based http request lib you can simply destroy token on the front-end inside the .then() part. It will be launched in the response .then() part after user executes this function (result code from the server endpoint must be ok, 200). After user clicks this route while searching for data, if database field user_enabled is false it will trigger destroying token and user will immediately be logged-off and stopped from accessing protected routes/pages. We don't have to await for token to expire while user is permanently logged on.

function searchForData() {   // front-end js function, user searches for the data
    // protected route, token that is sent along http request for verification
    var validToken = 'Bearer ' + whereYouStoredToken; // token stored in the browser 

    // route will trigger destroying token when user clicks and executes this func
    axios.post('/my-data', {headers: {'Authorization': validToken}})
     .then((response) => {
   // If Admin set user_enabled in the db as false, we destroy token in the browser localStorage
       if (response.data.user_enabled === false) {  // user_enabled is field in the db
           window.localStorage.clear();  // we destroy token and other credentials
       }  
    });
     .catch((e) => {
       console.log(e);
    });
}

IAM解决方案,如Keycloak(我曾经工作过)提供令牌撤销端点

令牌撤销端点 /领域/{域名}/协议/ openid-connect /撤销

如果只是想注销用户代理(或用户),也可以调用端点(这将使令牌无效)。同样,在Keycloak的情况下,依赖方只需要调用端点

/realms/{realm-name}/protocol/openid-connect/logout

链接以防万一,如果你想了解更多

没有使用jwt的刷新…

我想到了两种袭击的场景。一个是关于登录凭证的泄露。另一个是对JWT的盗窃。

For compromised login credentials, when a new login happens, normally send the user an email notification. So, if the customer doesn't consent to being the one who logged in, they should be advised to do a reset of credentials, which should save to database/cache the date-time the password was last set (and set this too when user sets password during initial registration). Whenever a user action is being authorized, the date-time a user changed their password should be fetched from database/cache and compared to the date-time a given JWT was generated, and forbid the action for JWTs that were generated before the said date-time of credentials reset, hence essentially rendering such JWTs useless. That means save the date-time of generation of a JWT as a claim in the JWT itself. In ASP.NET Core, a policy/requirement can be used to do do this comparison, and on failure, the client is forbidden. This consequently logs out the user on the backend, globally, whenever a reset of credentials is done.

For actual theft of JWT... A theft of JWT is not easy to detect but a JWT that expires easily solves this. But what can be done to stop the attacker before the JWT expires? It is with an actual global logout. It is similar to what was described above for credentials reset. For this, normally save on database/cache the date-time a user initiated a global logout, and on authorizing a user action, get it and compare it to the date-time of generation of a given JWT too, and forbid the action for JWTs that were generated before the said date-time of global logout, hence essentially rendering such JWTs useless. This can be done using a policy/requirement in ASP.NET Core, as previously described.

现在,你如何发现JWT被盗?目前我对此的回答是,偶尔提醒用户全局注销并重新登录,因为这肯定会让攻击者注销。

A good approach to invalidating a token would still need database trips. For a purpose that includes when some parts of the user record change, for example changing roles, changing passwords, email, and more. One can add a modified or updated_at field in the user record, which records the time of this change, and then you include this in the claims. So when a JWT is authenticated, you compare the time in the claims with the one recorded in the DB, if that of the claim was before, then token is invalid. This approach is also similar to storing the iat in the DB.

注意:如果您正在使用modified或updated_at选项,那么您还必须在用户登录和退出时更新它。