我读了很多关于JWT的文章,发现很难以安全的CSRF和XSS的方式使用它们
直到我意识到我的代币可能不会离开我的唯一域。。。这让我想到了实现的想法:
- 使用刷新令牌和访问令牌。这样,我可以为访问令牌设置短的过期时间,并限制数据库调用以验证用户,并且只有当访问令牌过期时,才可以通过在向用户发送新的访问令牌之前询问数据库刷新令牌未被列入黑名单来进行简单的验证
- 我知道,如果我将令牌存储在本地/会话存储中,它很容易受到XSS攻击,但如果我将其存储在仅限HTTP的cookie中,它将很容易受到CSRF攻击
- 由于我不需要访问JavaScript中的令牌,而且我只需要在一个网站中使用该令牌,我认为我可以将访问令牌存储在仅HTTP的cookie中(这样可以保护它免受XSS的影响),同时将安全标志和同一站点标志设置为strict(防止CSRF)
- 关于刷新令牌,我可以将其存储在一个仅HTTP的cookie中,该cookie具有相同的安全标志,但这次没有相同的站点标志。因为服务器永远不会只基于刷新令牌执行任何操作,所以我认为它不会受到CSRF攻击。服务器对刷新令牌所做的唯一一件事就是发回一个新的访问令牌,如果我理解得很好,就无法从CSRF攻击者那里读取该令牌。CSRF漏洞允许攻击者向服务器发出请求(服务器将自动包含仅HTTP cookie),但他无法读取此请求的响应
我不知道JWT的实现是否安全,或者我错过了什么。。。
这就是我要问你(JWT和网络安全专家)的问题,这会是一个好的JWT实现吗?
首先,关于JWT的一个词。老式的纯服务器端会话(具有长的随机会话id,正确存储在安全的httpOnly中,可能还有SameSite cookie)是";更安全";在大多数情况下比任何JWT都要好。原因是JWT将状态存储在客户端上,攻击者更容易使用它,可以对所涉及的加密进行离线攻击,而且JWT是纯文本的,只有它们的完整性受到保护(即,它们受到保护,不受客户端更改的影响,但默认情况下不受查看内容的影响,尽管您也可以使用JWE和加密的JWT)。最后但同样重要的是,实现更加复杂,简单性对安全性非常重要。
因此,当你需要JWT提供的福利时,你会使用它。
一个论点是它是无国籍的。然而,这往往被高估了。首先,在大多数应用程序中,瓶颈不会是在数据库中查找会话数据所需的数据库查找。在一些非常高调、高流量的应用程序中,它实际上可能是,但你可能不是每天都在开发这样的东西。通常,进行数据库查找是可以的,这会使整个过程简单得多。其次,有时您需要令牌吊销,即您希望能够强制用户注销。即使您使用JWT来处理无状态状态,您也必须在数据库中存储东西,例如吊销的令牌列表,并且检查这也将是数据库往返。撤销JWT根本不能以纯粹的无状态方式实现。
另一个好处是,您可以将其用于多种来源。如果你得到一个httpOnly会话cookie,它将由浏览器管理,javascript无法将其发送到其他来源,如API或其他什么地方-这就是关键,JS无法访问。但有时你确实需要这样做,尤其是在单点登录的情况下,你有一个身份提供者(一个提供登录的组件)和你想在其上使用访问令牌的服务(例如API)。在这种情况下,经典的cookie不起作用,JWT非常方便。
简言之,当你真的需要无国籍状态时,你会使用JWT,或者可以选择将代币发送到多个来源。根据经验,如果您可以将JWT存储在httpOnly cookie中,那么您可能根本不需要它,只需要使用一个简单的旧服务器端会话。
然后假设您决定使用JWT,那么刷新令牌呢。它们是基于令牌的身份验证的另一个通常被误解的特性。使用刷新令牌的目的是能够使用短时间的访问令牌,因此,如果访问令牌被泄露,它至少是有时间限制的。但是,如果你以与访问令牌相同的方式存储刷新令牌,为什么攻击者不会得到它呢?
只有当你以不同的方式存储刷新令牌时,才有意义,否则你可能只拥有长寿命的访问令牌,这不会降低安全性。
一个典型的事情是有一个身份提供者("登录服务器"或IdP),它在身份提供者来源的httpOnly cookie中设置刷新令牌(或只是一个普通的旧会话id),所以每当客户端向IdP发出请求时;只是工作";如果他们已经登录,并且可以在没有用户交互的情况下提供新的访问令牌。访问令牌然后存储在类似localStorage的东西中(用于服务源),易受XSS影响,但至少有时间限制。如果没有这种分隔,那么单独的刷新令牌可能没有多大意义。
最后要注意的是,所有这些应该很少由您自己实现。您使用的任何语言或框架都可能已经有一个或多个已知的良好、维护的身份验证实现。你应该使用这些,但当然,理解它仍然非常值得。
请注意,在任何实际的应用程序中,可能都会有一些微妙之处和某些情况在一定程度上改变您想要做的事情,但以上是考虑安全身份验证的一般方法。