首页 > 解决方案 > 将用户的权限存储在 JWT 声明中或在每次请求时在服务器上检查它是否更有效?

问题描述

JWT是确保发送给用户和返回的数据不被篡改的好方法,但这会导致一些艰难的选择。目前,我在选择将授权数据存储在 JWT 声明中并仅触摸数据库一次以进行授权,或者仅存储用户 ID 并使用数据库检查对服务器的每个请求的授权级别之间做出选择。

让这个选择变得如此艰难的原因在于,该应用程序使用多个授权级别,这使得 base64 编码的 url 相当长且庞大(见下文可以预期存储为授权级别的内容)。

另一方面,要获得授权,需要在数据库中进行两次查找。

所以我的问题如下;通过将权限发送到服务器的每个请求的额外开销是否值得避免在每个请求上查找权限的麻烦?

作为旁注;在权限更改的情况下,在数据库中查找方法的好处是不需要用户再次登录(参见帖子)。

"perms": {
    "roles": [
        {
            "name": "Admin",
            "id": 1,
            "assigned": true
        },
        {
            "name": "Webmaster",
            "id": 8,
            "assigned": true
        }
    ],
    "actions": [
        {
            "id": 1,
            "name": "cms-edit",
            "parameters": null,
            "parameterized": null
        },
        {
            "id": 9,
            "name": "admin-syslog",
            "parameters": null,
            "parameterized": null
        },
        {
            "id": 10,
            "name": "admin-debug",
            "parameters": null,
            "parameterized": null
        },
        {
            "id": 12,
            "name": "member-list-extended",
            "parameters": null,
            "parameterized": null
        },
        {
            "id": 2,
            "name": "cms-list",
            "parameters": null,
            "parameterized": null
        },
        {
            "id": 3,
            "name": "cms-add",
            "parameters": null,
            "parameterized": null
        },
        {
            "id": 5,
            "name": "member-list",
            "parameters": null,
            "parameterized": null
        },
        {
            "id": 7,
            "name": "member-view",
            "parameters": null,
            "parameterized": null
        },
        {
            "id": 8,
            "name": "member-edit",
            "parameters": null,
            "parameterized": null
        }
    ]

标签: httpsecurityoptimizationauthorizationjwt

解决方案


你的第一个问题:

通过将权限发送到服务器的每个请求的额外开销是否值得避免在每个请求上查找权限的麻烦?

回答:

让我们看一下 jwt.io 提供的关于何时使用 JWT 的描述:

授权:这是使用 JWT 最常见的场景。用户登录后,每个后续请求都将包含 JWT,从而允许用户访问该令牌允许的路由、服务和资源。单点登录是当今广泛使用 JWT 的一项功能,因为它的开销很小并且能够在不同的域中轻松使用。

这意味着一旦用户登录,您需要在服务器端生成令牌。

它包含:

  • 用户(作为 id 或名称)
  • 客户端拥有的角色(用户、管理员、访客,等等……)

一旦客户端向服务器请求或发送数据,服务器首先检查给定令牌是否有效且已知,然后检查角色是否满足访问特定资源的条件。

所有角色/访问数据都可以在系统启动时读取一次并保存在内存中。此外,客户端拥有的角色只会在客户端登录时从数据库中读取一次。这样您就没有后续的数据库访问权限,因此性能大大提高。

另一方面,如果客户端请求数据或想要执行操作,您需要一种身份验证机制来评估传递的令牌是否获得了执行此操作所需的角色。

这样我们解决了数据库的麻烦,而且我们消除了向客户端暴露太多信息的危险(即使客户端不能篡改数据,它可以读取数据!)

请注意:https ://jwt.io/introduction

请注意,使用签名令牌,令牌中包含的所有信息都会向用户或其他方公开,即使他们无法更改。这意味着您不应将秘密信息放入令牌中。

参见 A3(敏感数据暴露):https ://www.owasp.org/index.php/Top_10-2017_Top_10

最后:如果客户端空闲时间过长或故意注销,也要使令牌无效。

后续问题:

权限更改的情况下,在数据库中查找方法的好处是不需要用户再次登录

回答:

根据您的服务器的基础设施,您可以编写刷新机制(如果角色更新,服务器会生成一个新令牌并将其与生成的答案一起发送给客户端,使旧令牌无效,客户端仅使用最近的令牌并覆盖旧)或在服务器端添加一些状态,如客户端会话:

消除令牌上的角色/权限。您最好为客户端生成会话并在服务器端提供会话角色/权限。客户端获取它可以进行身份​​验证的会话令牌(通常是一个 id)。一旦权限/角色发生变化,我们必须做两件事:

  1. 更新数据库
  2. 更新会话的角色/权限

同样,每个后续请求都将在内存中进行角色/权限检查,并且不需要数据库通信,而客户端只有一个小的会话令牌(或您的 JWT)。因此,角色/权限更改对客户端是透明的(不需要重新登录),并且我们消除了 JWT 刷新要求。


推荐阅读