在 react.js 中存储访问令牌的位置?
Posted
技术标签:
【中文标题】在 react.js 中存储访问令牌的位置?【英文标题】:Where to store access-token in react.js? 【发布时间】:2018-08-05 15:01:59 【问题描述】:我正在用 Reactjs 构建一个应用程序。在验证 access_token 后,我必须进行 fetch 调用。注册时,从后端服务器获取 access_token。但是,在哪里存储这些 access_token。有什么方法可以使这些 access_token 全局化,以便所有组件都可以访问它。我使用了本地存储、缓存和会话存储,但这些都是不可取的。 过去几天一直在这个问题上,任何解决方案。提前致谢。
【问题讨论】:
你在使用 redux 吗? 你可以把它绑定到window对象上,也可以把它放到你的redux store的初始状态。 但在刷新时,access_token 将为空或未定义! @codebased,我使用firebase
存储access-token
。
我认为 localStorage 完全没问题。如果您担心,请使用一些简单的身份验证令牌(JWT 不能过期)并给它一个短暂的过期时间,例如 1 小时。另外,如果您想保护一些关键操作(例如付款或某事),请设置这些密码。
【参考方案1】:
可用选项和限制:
有两种存储令牌的选项:
-
Web Storage API:提供 2 种机制:
sessionStorage
和 localStorage
。此处存储的数据将始终可供您的 javascript 代码使用,并且无法从后端访问。因此,例如,您必须手动将其添加到您的请求中。此存储仅适用于您应用的域,而不适用于子域。这两种机制的主要区别在于数据过期:
sessionStorage
:数据仅可用于会话(直到浏览器或选项卡关闭)。
localStorage
: 存储没有过期日期的数据,只能通过 JavaScript 清除,或者清除浏览器缓存/本地存储的数据
-
Cookies:随后续请求自动发送到您的后端。可以控制 Javascript 代码的到期和可见性。可用于您应用的子域。
在设计身份验证机制时必须考虑两个方面:
安全性:访问或身份令牌是敏感信息。要始终考虑的主要攻击类型是 Cross Site Scripting (XSS) 和 Cross Site Request Forgery (CSRF)。 功能要求:关闭浏览器时用户是否应该保持登录状态?他的会议将持续多长时间?等等出于安全考虑,OWASP 不建议将敏感数据存储在 Web 存储中。您可以查看他们的CheatSheetSeries 页面。你也可以阅读this detailed article了解更多详情。
原因主要与XSS漏洞有关。如果您的前端不是 100% 防止 XSS 攻击,那么恶意代码可以在您的网页中执行,并且可以访问令牌。 完全防 XSS 是非常困难的,因为它可能是由您使用的 Javascript 库之一引起的。
另一方面,如果将 Cookie 设置为 HttpOnly
,则 Javascript 可能无法访问它们。
现在 cookie 的问题是它们很容易使您的网站容易受到 CSRF 的攻击。 SameSite
cookie 可以缓解这种类型的攻击。但是,旧版本的浏览器不支持这种类型的 cookie,因此可以使用其他方法,例如使用状态变量。此 Auth0 文档article 中有详细说明。
建议的解决方案:
为了安全地存储您的令牌,我建议您组合使用 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 的过期时间以满足您应用的要求。
Peter Locke 在this article 中提出并很好地描述了这个想法。
【讨论】:
那么在第 3 步中,你将如何从浏览器中提取 cookie 并将其附加到标题中? 第 3 步用于后端服务。它将收到 2 个单独的 cookie。因此,与其读取一个 cookie,不如检查其中的 2 个。此步骤不涉及浏览器。 我明白了。但是,您必须将 cookie 传递到后端。如果使用 axios,如何在 ui 中提取一个 httpOnly cookie,将其附加到Authorization
标头?目前,我可以简单地使用withCredentials: true
以常规headers.cookies
发送cookie,但我不知道如何在授权标头中发送它们
您不会在 Authorization
标头中发送它们。 HttpOnly
cookie 对您的 Javascript 代码不可见(这就是这种方法的目标)。在您的后端中间件中,您将收到 2 个 cookie 并将它们合并到 Authorization
标头中。
所以你的意思是你将后端的cookie合并到授权头之前它们可以被各种控制器使用?【参考方案2】:
虽然迟到了,但我还是想分享我对这个话题的看法。 Anouar 给出了一个很好的答案,包括被认为是针对 XSS 保存的仅 http cookie,指出了 CSRF 漏洞并链接了 Peter Locke 的文章。
但是,就我而言,我需要应用程序是 100% 无状态的,这意味着我不能使用 cookie。
从安全的角度来看,将访问令牌存储在持久位置(如 localStorage、window 等)是不好的做法。因此,您可以使用 redux(或内置在 state/context 中的 react.js)将 JWT 存储在变量中。这将保护令牌免受上述攻击,但在刷新页面后将其设为空。
我解决这个问题的方法是使用我存储在 localStorage 中的刷新令牌(如果您愿意,可以使用会话存储或类似的存储)。该刷新令牌的唯一目的是获取新的访问令牌,并且后端确保刷新令牌不被盗(例如,实现一个被检查的计数器)。 我将访问令牌保存在缓存中(我的应用程序中的一个变量),一旦由于重新加载而过期或丢失,我使用刷新令牌来获取新的访问令牌。
显然,这仅在您还构建后端时才有效(或至少在后端实现刷新令牌时)。如果您处理的现有 API 没有实现刷新令牌等,并且将访问令牌保存在变量中不是您的选择(由于重新加载时为空),您还可以在您之前使用应用程序机密加密令牌将其保存到 localStorage (或会话存储,或者......是的,你明白了)。请注意,解密令牌需要一些时间,并且会降低您的应用程序的速度。因此,您可以将加密的令牌保存到 localStorage(或...)并在刷新后仅解密一次,然后将其保存在 state/redux 变量中,直到您再次刷新/再次从 localStorage 解密等。
关于这个主题的最后一句话:身份验证是应用程序的关键基础设施,尽管有趣的游戏和在线银行之间存在明显的区别(您可能想对那家银行“偏执”,而只是“关心” 关于游戏),诸如“localStorage 完全没问题”或“在最坏的情况下会发生什么?1 小时后过期”之类的答案是危险的,而且完全是错误的。机器可以在几秒钟内造成很大的伤害,你不想留下那个空隙。如果您懒得保护您的应用,也许您想使用现有解决方案而不是构建自己的解决方案。
也就是说,JWT / token auth 对游戏来说是相当新的(几年,但不像开发中的其他主题那样成熟)。找到一个可行的解决方案需要一些时间和精力,但让我们继续构建安全的软件,而不是让网络上充斥着快速的黑客攻击。
最好的,快乐的编码。
【讨论】:
惊人的答案!谢谢兰明 :) 很棒的遮阳篷正是我需要听到的 laravel 后端,护照已经使用 refresh_tokens,我不知道它们的用途。 很好的答案,但您不应该将刷新令牌存储在本地或会话存储中。服务器是唯一需要读取刷新令牌的服务器,您可以通过将其存储在 HttpOnly 并将安全设置为 true 的 cookie 中来做到这一点 我同意 Udendu Abasili。我的答案集中在 100% 无状态的方法上。这就是为什么我决定将加密令牌存储在本地或会话 cookie 中。但是,如果可能,我通常也建议使用(加密的)HttpOnly cookie。 我的意思是“本地或会话存储”,很明显。刚刚意识到我在前面的评论中犯了这个错误。【参考方案3】:Michael Washburn 有一篇关于如何使用 redux 持久化状态的非常好的文章,here on his webpage
在文章中,他有一个指向由 Redux 的合著者之一 Dan Abramov 创建的 very descriptive video tutorial 的链接,我跟着他将它添加到我的项目中。 这是我用来使它工作的代码:
store.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;
;
localStorage.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"));
【讨论】:
像这样存储令牌是一个安全问题。 @Darbio 为什么这是一个安全问题,否则你怎么做? 查看此链接以获取有关为什么这是一个坏主意的更多信息:github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/… 另一种方法是设置一个只有服务器才能读取的安全、仅限 http 的 cookie。此 cookie 随每个请求一起发送,无法使用 javascript 读取。 如果没有后端可以接受吗?显然不建议使用 accessTokens。但是,如果客户端直接调用 API,这似乎是唯一的方法?以上是关于在 react.js 中存储访问令牌的位置?的主要内容,如果未能解决你的问题,请参考以下文章
我可以在哪里将 JWT 令牌存储在 React js 中以验证应用程序后端的各种路由?
在 .net 核心 web api 中存储 JWT 令牌的位置?