我正在 Reactjs 中构建一个应用程序。在验证 access_token 后,我必须进行 fetch 调用。注册时,access_token 是从后端服务器获取的。但是,在哪里存储这些 access_token。有没有办法让这些 access_token 成为全局的,以便所有组件都可以访问它。我使用过本地存储、缓存和会话存储,但这些都是不可取的。这几天一直在纠结这个问题,求解决方法。提前谢谢。
在 react.js 中存储访问令牌的位置?
可用选项和限制:
有两种类型的选项可用于存储您的令牌:
- Web Storage API:它提供 2 种机制:
sessionStorage
和localStorage
. 存储在此处的数据将始终可供您的 Javascript 代码使用,并且无法从后端访问。因此,例如,您必须手动将其添加到您的请求中的标头中。此存储仅适用于您应用的域,而不适用于子域。这两种机制的主要区别在于数据过期:
sessionStorage
:数据仅可用于会话(直到浏览器或选项卡关闭)。localStorage
: 存储没有过期日期的数据,只能通过 JavaScript 清除,或清除浏览器缓存/本地存储的数据
- Cookies:随后续请求自动发送到您的后端。可以控制您的 Javascript 代码的到期时间和可见性。可用于您应用的子域。
在设计身份验证机制时,您必须考虑两个方面:
出于安全考虑,OWASP不建议将敏感数据存储在 Web 存储中。您可以查看他们的CheatSheetSeries页面。您还可以阅读这篇详细的文章以了解更多详细信息。
原因主要与XSS漏洞有关。如果您的前端不是 100% 抵御 XSS 攻击,那么恶意代码可能会在您的网页中执行,并且可以访问令牌。完全防 XSS 非常困难,因为它可能是由您使用的 Javascript 库之一引起的。
另一方面,如果 Cookie 设置为HttpOnly
. 现在 cookie 的问题是它们很容易使您的网站容易受到 CSRF 的攻击。SameSite
cookie 可以减轻这种类型的攻击。但是,旧版本的浏览器不支持这种类型的 cookie,因此可以使用其他方法,例如使用状态变量。这篇 Auth0 文档文章中对此进行了详细说明。
建议的解决方案:
为了安全地存储您的令牌,我建议您使用 2 个 cookie 的组合,如下所述:
JWT 令牌具有以下结构: header.payload.signature
通常,有效载荷中存在有用的信息,例如用户角色(可用于调整/隐藏 UI 的一部分)。因此,让该部分对 Javascript 代码可用非常重要。
身份验证流程完成并在后端创建 JWT 令牌后,其想法是:
- 将
header.payload
部件存储在SameSite
Secure
Cookie 中(因此只能通过 https 使用,并且仍可用于 JS 代码) - 将
signature
零件存储在SameSite
Secure
HttpOnly
Cookie 中 - 在后端实现一个中间件,以从这 2 个 cookie 中重新构建 JWT 令牌并将其放入标头中:
Authorization: Bearer your_token
您可以为 cookie 设置到期时间以满足您的应用程序的要求。
虽然参加晚会很晚,但我想分享我对这个话题的看法。 Anouar 给出了一个很好的答案,包括被认为是防止 XSS 的 http-only cookie,指出了 CSRF 漏洞并链接了 Peter Locke 的文章。
但是,就我而言,我需要应用程序 100% 无状态,这意味着我不能使用 cookie。
从安全的角度来看,将访问令牌存储在持久位置(如 localStorage、window 等)是不好的做法。因此,您可以使用 redux(或在 state/context 中构建的 react.js)将 JWT 存储在变量中。这将保护令牌免受上述攻击,但在页面刷新后将其置空。
我为解决这个问题所做的是使用刷新令牌,我将其存储在 localStorage 中(如果您愿意,可以使用会话存储或类似存储)。该刷新令牌的唯一目的是获得一个新的访问令牌,后端确保刷新令牌不被窃取(例如实现一个被检查的计数器)。我将访问令牌保存在缓存中(我的应用程序中的一个变量),一旦由于重新加载而过期或丢失,我将使用刷新令牌来获取新的访问令牌。
显然,这仅在您还构建后端时才有效(或者至少如果后端实现了刷新令牌)。如果您处理未实现刷新令牌等的现有 API,并且将访问令牌保存在变量中不是您的选择(由于重新加载时为 null),您还可以在您之前使用应用程序机密对令牌进行加密将其保存到 localStorage(或会话存储,或者...是的,您明白了)。请注意,解密令牌需要一些时间并且会降低您的应用程序的速度。因此,您可以将加密的令牌保存到 localStorage(或...)并在刷新后仅对其解密一次,然后将其保持在状态/redux 变量中,直到您再次刷新/再次从 localStorage 解密等。
关于这个主题的最后一句话:身份验证是应用程序的关键基础设施,虽然有趣的游戏和网上银行之间存在明显的区别(你可能想对那家银行“偏执”,而只“关心”游戏),诸如“localStorage 完全没问题”或“在最坏的情况下会发生什么?1 小时后过期”之类的答案是危险的,而且完全是错误的。机器可以在几秒钟内造成很多伤害,您不想让这个差距保持开放。如果您懒得保护您的应用程序,也许您想使用现有的解决方案而不是构建自己的解决方案。
也就是说,JWT/令牌身份验证对游戏来说是相当新的(几年,但不如开发中的其他主题成熟)。找到可行的解决方案需要一些时间和精力,但让我们继续构建安全的软件,而不是让网络泛滥成灾。
最好的,快乐的编码。
Michael Washburn在他的网页上有一篇关于如何使用 redux 保持状态的非常好的文章
在文章中,他提供了一个非常具有描述性的视频教程的链接,该教程由 Redux 的合著者之一 Dan Abramov 创建,我跟随他将其添加到我的项目中。这是我用来使其工作的代码:
商店.js
import { createStore, combineReducers } from "redux";
import { UserReducer, CopyReducer } from "../reducers";
import { loadState, saveState } from "../utils/localStorage";
export const giveMeStore = () => {
const reducers = combineReducers({
copy: CopyReducer,
user: UserReducer
});
const persistedState = loadState();
const store = createStore(reducers, persistedState);
//user contains the TOKEN
store.subscribe(() => {
saveState({
user: store.getState().user
});
});
return store;
};
本地存储.js
export const loadState = () => {
try {
const serializedState = localStorage.getItem("state");
if (serializedState === null) {
return undefined;
}
return JSON.parse(serializedState);
} catch (err) {
return undefined;
}
};
export const saveState = state => {
try {
const serializedState = JSON.stringify(state);
localStorage.setItem("state", serializedState);
} catch (err) {
//ignoring write erros
}
};
并将商店添加到提供者:
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { giveMeStore } from "./store.js";
const Root = () => {
return (
<Provider store={giveMeStore()}>
//... your components
//...
</Provider>
);
};
ReactDOM.render(<Root />, document.querySelector("#root"));