如何处理刷新令牌

信息安全 验证 访问控制 jwt
2021-09-01 07:41:08

我试图围绕基于 JWT(JSON Web 令牌)的身份验证来使用访问令牌保护 API 端点。我通过要求用户拥有有效的访问令牌以获得所述资源来保护这些端点。

但是,我正在努力理解使用刷新令牌。

我有一个接受POST请求的 /auth/login 端点:

class UserLogin(Resource):
    def post(self):
        data = parser.parse_args()
        current_user = User.find_by_username(data['username'])
        if not current_user:
            return {'message': 'Unsuccessful. You do not have an account'}

        if User.verify_hash(data['password'], current_user.password):
            access_token = create_access_token(current_user)
            refresh_token = create_refresh_token(current_user)
            return {
                'message': 'Login successful {}'.format(current_user.username),
                'roles': 'Role: {}'.format(current_user.role),
                'access token': access_token,
                'refresh token': refresh_token
            }
        else:
            return {'message': 'Something went wrong'}

端点本质上检查传入的凭据以确保用户存在于我的用户存储中。如果他们这样做,我将返回一个访问和刷新令牌。否则,错误消息将被客户端处理。

现在,假设用户已经登录并尝试从另一个需要访问令牌来保护的端点访问私有资源。客户端将发出请求,受保护的端点将返回一些错误消息,说明它们没有访问令牌。然后,客户端将尝试使用登录时获得的刷新令牌生成新的访问令牌:

class TokenRefresh(Resource):
    @jwt_refresh_token_required
    def post(self):
        current_user = get_jwt_identity()
        access_token = create_access_token(identity=current_user)
        return {'access token': access_token}

点击上述资源将生成一个新的访问令牌,其中包含一些已定义的过期时间。这是我目前的流程。

我的问题是,我应该如何存储刷新令牌以确保经过身份验证的用户可以访问它们?我的登录请求中是否应该有一些额外的逻辑,将生成的刷新令牌存储给该特定用户?

如果是这样,我是否应该在每次客户端尝试刷新其访问令牌时生成一个新的刷新令牌并覆盖附加到用户的现有刷新令牌?

目前,我觉得如果攻击者设法获得刷新令牌,我的进程可能会受到损害。

2个回答

拥有访问令牌的要点是可以在不检查失效的情况下使用它们。您可以拥有 10000 个前端服务器,用户可以使用令牌访问,而无需询问某些数据库是否无效。但是一段时间后,令牌会过期。用户需要一个新的访问令牌,发送她的刷新令牌,然后在某个数据库中检查这个刷新令牌。您永远不需要检查过期的访问令牌或拥有任何状态,而是将滥用限制在令牌的生命周期内。

如果您不需要接受令牌而不检查数据库中的过期时间,则不需要这两个不同的令牌。您可以只为每次访问使用刷新令牌。

示例工作流程为:

  1. 用户登录,获取访问和刷新令牌。访问令牌生命周期 15 分钟,刷新令牌 5 天。

  2. 用户使用访问令牌访问服务。服务只检查签名和生命周期。没有数据库连接。

  3. 用户注销,刷新令牌在数据库中被标记为过期
  4. 用户使用访问令牌访问服务,这仍然有效
  5. 15分钟通过。访问令牌过期,用户使用仍在其生命周期内的刷新令牌请求新的访问令牌。该服务检查数据库并发现令牌已过期。用户无法获得新的访问令牌。

整个系统是在使会话无效所需的时间和所需的共享/同步数据源的连接数量之间进行权衡。

您可以在每次刷新时替换刷新令牌,但请记住,您需要存储所有过期的刷新令牌,直到它们的生命周期结束

从安全角度来看,创建新令牌是有意义的,但它是安全性和数据库中数据量之间的权衡。

在最坏的情况下(刷新令牌没有生命周期,永远不要这样做),您现在需要每隔几分钟在数据库中为每个用户存储一个令牌,而不是为每个用户存储一个令牌,并且您永远无法再次删除它们。

根据我的经验,刷新令牌是实践和理论不匹配的技术之一。理论上,你发出一个登录请求,然后取回一个访问令牌(生命周期很短)和一个刷新令牌(有一个很长的有效期,没有过期,并且可以随时用于获取新的访问令牌)。如果客户端尝试发送过期的访问令牌,并从服务器获得拒绝,它可以发送刷新令牌,获取新的访问令牌,然后继续。

在这种情况下,很明显刷新令牌确实很强大,并且需要小心存储(例如,在移动设备上的凭证存储中,而不是浏览器 cookie,或者在仔细保护的服务器端存储中)。但是,规范确实允许身份验证服务器使刷新令牌无效。

因此,在实践中,刷新令牌在登录时发出并在注销或一段时间不活动后失效的情况并不少见。在这种情况下,它可以是服务器上会话数据的一部分,因此会自动链接到当前用户。任何其他用户只能在经过身份验证的会话中使用刷新令牌,这意味着将其链接到特定用户的任何逻辑也会失败。

从上面的代码片段中,这几乎就是您所拥有的:例如,每次登录都会生成刷新令牌,而不是在创建帐户时生成之后从存储中检索。唯一缺少的部分是如何查询刷新令牌 - 这暗示刷新令牌被认为足以获得访问令牌,而不是经过身份验证的会话和刷新令牌的组合。正如您所怀疑的那样,这是不安全的。

刷新令牌提供获取新访问令牌的授权,但不验证请求访问令牌的人是否应该具有访问权限。您需要在接受授权之前提供身份验证步骤,并确保每次使用刷新令牌时都使用此步骤 - 打开会话可能就足够了。

您可以选择在每个新访问令牌上替换刷新令牌。如果多个用户尝试使用刷新令牌(第二个失败)因此限制并发使用,这具有相当明显的破坏的副作用。但是,避免在旧令牌过期的情况下发布新的刷新令牌,因为这会增加令牌泄露的可能性。在刷新令牌随会话到期的情况下(假设会话生命周期较短),它的好处可能有限,但可以帮助更长的会话(例如“记住我”功能)。