为什么不将两个 access_tokens 和两个 refresh_tokens 存储在 cookie 中,一个存储在本地存储中,以防止 XSS 和 CSRF
Why not to have two access_tokens and two refresh_tokens stored one in cookie and one in localstorage to protect from XSS and CSRF
背景
场景是我们有一个 REACT SPA 和一个既是资源服务器又是身份验证服务器的 API。我们想要实现基于简单令牌的身份验证。没有专用的 SSO 服务器,因此不需要 OAuth2。
过去两天我阅读了很多关于如何正确执行此操作的文章,但有一件事仍然困扰着我。有很多关于令牌应该存储在 LocalStorage 还是 cookie 中的讨论? First 容易受到 XSS 攻击,因为注入的代码可以从 LocalStorage 窃取您的数据,但可以防止 CSRF,因为来自恶意网站的伪造请求将无法窃取它。后者容易受到 CSRF 的攻击,因为来自恶意网站的伪造请求会使浏览器一起发送 cookie,但可以防止 XSS,因为恶意网站无法访问原始网站的 LocalStorage。
问题:那为什么不两者都用呢?
为什么不body 创建两个用两个不同的密钥和两个不同的 refresh_token 秘密签名的 JWT?一个 JWT 和 refresh_token 然后存储在 LocalStorage 中,另一个存储在 cookie 中。
如果我 ask google 总是一对一。以任何方式同时拥有反模式或坏主意吗?因为实现它并不是那么多工作...
我认为这应该如何工作
以下是我认为这种流程的工作原理:
- 用户在登录表单中输入登录名和密码
- React 应用程序将带有登录名和密码的登录请求发送到
/api/auth/login
。在 return 中得到:
- 在 body 中:JWT
access_token
由 private_key1
签名,一些秘密 refresh_token
为该特定用户生成并存储在某种持久层中
- 作为
httpOnly
cookie:JWT access_token
由 private_key2
签名
- 作为路径设置为
/api/auth/refresh
的 httpOnly
cookie:为该特定用户生成的一些秘密 refresh_token
并存储在某种持久层 即不同于 returned in body
- React 应用在每个下一个请求中发送第一个 JWT 作为
Authentication
header 的值,第二个 JWT 由浏览器作为 cookie 自动附加。
- API 检查两者的签名,如果两者都正确则对请求进行身份验证
- 3 - 4 次重复,直到任何令牌过期(我假设它们的过期时间相同,但为了这一步,让我们更通用)
- React 应用使用 JWT 和
refresh_token
从 LocalStorage 向 /api/auth/refresh
发送请求,浏览器将第二个 JWT 作为 cookie 附加,另一个 refresh_token
作为 cookie 附加,因为路径满足 cookie 的条件。
- API 验证过期 JWT 的签名并检查
refresh_token
s 是否存在于该用户的持久层中并且没有过期。如果一切正常,将生成新的 JWT 和新的 refresh_token
s 并以与第 2 点相同的方式 returned 到 React APP。
注销时刷新令牌从持久层中删除,因此访问令牌不会被刷新。
如果您认为此流程更安全,为什么需要此刷新令牌?
我得出的结论是,添加刷新令牌确实不会增加用户的安全性,缺点是在 logout/revoke 之后 JWT 仍然有效但刷新令牌是revoked/user 注销了,但我从这个门户网站的许多讨论中设法收集了很多其他专业人士:
- 只验证 JWT 的签名比检查两个签名要快得多,如果它还没有 revoked/user 在持久层(可能是数据库)中注销。这些检查必须只对频率低得多的刷新令牌进行。
- 刷新令牌,因为频率较低,可能需要更长的时间,这让我们可以在这里做一些额外的验证,例如,用户 IP(如果自上次刷新后没有突然更改为中国)或数字在过去 4 分钟等时间里,请求的数量没有显着增加。
- 因为刷新令牌更为重要,所以您可以更加专注于保护这个端点 - 例如,如果您记录请求和响应,access_token 更有可能在您的日志中泄漏,但是当看到涉及
/api/auth/refresh
的拉取请求您的团队可能有一项政策要求所有团队成员都需要接受它,也许还有来自安全团队的人
- JWT 中的声明每 4 分钟刷新一次(或您为短期 JWT 选择的任何到期时间)
- 如果您的私钥泄露,您可以即时更改它们。它会立即使所有现有的 JWT 失效,但刷新令牌是安全的,因此用户不会注销。相反,他们的 JWT 将在后台刷新下一个请求(当然,如果在前面正确实施)。
我们已经实现了这个流程,这里有两个主要结论:
- 当 API 和前端托管在同一域(例如,api.example.com 和 example.com)时,此流程非常有效并提供更高的安全性
httpOnly
当 API 和前端托管在不同的域(例如,frontend.com 和 api.com)时,Safari 浏览器会阻止 cookie
背景
场景是我们有一个 REACT SPA 和一个既是资源服务器又是身份验证服务器的 API。我们想要实现基于简单令牌的身份验证。没有专用的 SSO 服务器,因此不需要 OAuth2。
过去两天我阅读了很多关于如何正确执行此操作的文章,但有一件事仍然困扰着我。有很多关于令牌应该存储在 LocalStorage 还是 cookie 中的讨论? First 容易受到 XSS 攻击,因为注入的代码可以从 LocalStorage 窃取您的数据,但可以防止 CSRF,因为来自恶意网站的伪造请求将无法窃取它。后者容易受到 CSRF 的攻击,因为来自恶意网站的伪造请求会使浏览器一起发送 cookie,但可以防止 XSS,因为恶意网站无法访问原始网站的 LocalStorage。
问题:那为什么不两者都用呢?
为什么不body 创建两个用两个不同的密钥和两个不同的 refresh_token 秘密签名的 JWT?一个 JWT 和 refresh_token 然后存储在 LocalStorage 中,另一个存储在 cookie 中。 如果我 ask google 总是一对一。以任何方式同时拥有反模式或坏主意吗?因为实现它并不是那么多工作...
我认为这应该如何工作
以下是我认为这种流程的工作原理:
- 用户在登录表单中输入登录名和密码
- React 应用程序将带有登录名和密码的登录请求发送到
/api/auth/login
。在 return 中得到:
- 在 body 中:JWT
access_token
由private_key1
签名,一些秘密refresh_token
为该特定用户生成并存储在某种持久层中 - 作为
httpOnly
cookie:JWTaccess_token
由private_key2
签名
- 作为路径设置为
/api/auth/refresh
的httpOnly
cookie:为该特定用户生成的一些秘密refresh_token
并存储在某种持久层 即不同于 returned in body
- React 应用在每个下一个请求中发送第一个 JWT 作为
Authentication
header 的值,第二个 JWT 由浏览器作为 cookie 自动附加。 - API 检查两者的签名,如果两者都正确则对请求进行身份验证
- 3 - 4 次重复,直到任何令牌过期(我假设它们的过期时间相同,但为了这一步,让我们更通用)
- React 应用使用 JWT 和
refresh_token
从 LocalStorage 向/api/auth/refresh
发送请求,浏览器将第二个 JWT 作为 cookie 附加,另一个refresh_token
作为 cookie 附加,因为路径满足 cookie 的条件。 - API 验证过期 JWT 的签名并检查
refresh_token
s 是否存在于该用户的持久层中并且没有过期。如果一切正常,将生成新的 JWT 和新的refresh_token
s 并以与第 2 点相同的方式 returned 到 React APP。
注销时刷新令牌从持久层中删除,因此访问令牌不会被刷新。
如果您认为此流程更安全,为什么需要此刷新令牌?
我得出的结论是,添加刷新令牌确实不会增加用户的安全性,缺点是在 logout/revoke 之后 JWT 仍然有效但刷新令牌是revoked/user 注销了,但我从这个门户网站的许多讨论中设法收集了很多其他专业人士:
- 只验证 JWT 的签名比检查两个签名要快得多,如果它还没有 revoked/user 在持久层(可能是数据库)中注销。这些检查必须只对频率低得多的刷新令牌进行。
- 刷新令牌,因为频率较低,可能需要更长的时间,这让我们可以在这里做一些额外的验证,例如,用户 IP(如果自上次刷新后没有突然更改为中国)或数字在过去 4 分钟等时间里,请求的数量没有显着增加。
- 因为刷新令牌更为重要,所以您可以更加专注于保护这个端点 - 例如,如果您记录请求和响应,access_token 更有可能在您的日志中泄漏,但是当看到涉及
/api/auth/refresh
的拉取请求您的团队可能有一项政策要求所有团队成员都需要接受它,也许还有来自安全团队的人 - JWT 中的声明每 4 分钟刷新一次(或您为短期 JWT 选择的任何到期时间)
- 如果您的私钥泄露,您可以即时更改它们。它会立即使所有现有的 JWT 失效,但刷新令牌是安全的,因此用户不会注销。相反,他们的 JWT 将在后台刷新下一个请求(当然,如果在前面正确实施)。
我们已经实现了这个流程,这里有两个主要结论:
- 当 API 和前端托管在同一域(例如,api.example.com 和 example.com)时,此流程非常有效并提供更高的安全性
httpOnly
当 API 和前端托管在不同的域(例如,frontend.com 和 api.com)时,Safari 浏览器会阻止 cookie