【问题标题】:Next.js is same-site-origin by default but I can still access itNext.js 默认是同站点来源,但我仍然可以访问它
【发布时间】:2023-03-23 23:38:01
【问题描述】:

我想知道如何保护我的 api 路由。文档说,默认情况下,api 路由是相同的站点来源。

API 路由不指定 CORS 标头,这意味着它们仅在默认情况下是同源的。您可以通过使用 cors 中间件包装请求处理程序来自定义此类行为。 Next.js Documentation

但如果我使用像 Postman 这样的请求工具,我总是可以调用它并获得结果:

// Next.js API route support: https://nextjs.org/docs/api-routes/introduction

export default (req, res) => {
  res.status(200).json({ name: 'John Doe' })
}

这怎么可能?我只想限制对我的应用程序的访问。

【问题讨论】:

  • 非常感谢您清理问题。我想在我的数据库中保存一个秘密并在我的应用程序请求端点以确保只有应用程序可以获取数据时调用它。我只是不知道如何调用该秘密,因为我在客户端的应用程序中执行我的请求,这些请求将在代码中执行。我可以将秘密保存在 env 变量中,但如果请求来自我的应用程序,我需要在 api 上读取它。为此,我需要知道如何将请求检测为来自我的应用程序而不是来自任何其他来源的收入。你能告诉我如何检测吗?
  • (我对 Next.js 还是很陌生)如果我只是从我的 api 路由中获取代码并创建一个正常的函数,而我的 api 路由我会要求秘密怎么办?然后应用程序本身将使用正常功能,并且每个其他来源只有在他们有我提供的凭据时才能访问 api 端点。这种方法怎么样? (我只是不知道如何做我的数据库功能,因为它唯一的工作服务器端而不是客户端)
  • 感谢您的回复。你有关于 req.header.host 的任何来源或例子吗?我是否正确地在我的 api 端点上的 if 语句中请求 req.headers.host 以验证它是我的应用程序执行请求?

标签: next.js


【解决方案1】:

我发现https://github.com/vercel/next.js/blob/canary/examples/cms-contentful 非常有用,可以帮助我解决同样的问题,即不再使用 API 路由作为页面等的主要数据检索。在这个例子中,他们的数据检索代码在 /lib/api.js文件(不在 API 路由中)。无论是使用页面 (https://github.com/vercel/next.js/blob/canary/examples/cms-contentful/pages/posts/%5Bslug%5D.js) 还是 API (https://github.com/vercel/next.js/blob/canary/examples/cms-contentful/pages/api/preview.js) 检索数据,数据检索和数据库连接都不会出现在您的 API 路由中。如果您只希望您的应用页面访问数据,我怀疑您可以跳过使用 API 路由。

这是另一个非常简单的沙盒示例(但我建议先查看上面的 GitHub 示例):

https://codesandbox.io/s/data-retrieval-outside-api-7zlmn?file=/pages/index.js

【讨论】:

  • 当我第一次阅读这篇文章时,我只是想:哇,这似乎是一个很棒的方法。进一步思考,我们应该考虑一些问题。在我的情况下,我使用 prisma 作为数据库设置,这对于下一个项目来说实际上很常见。可能它只在 geterersideprops 和 api 路由中起作用。我们无法在 /lib/api.js 中调用它。我们遇到的第二个问题:如果我们从正在使用 api 密钥访问它的服务中获取数据,我们会将其保存在 env var 中以使其私有。获取 env var 的值也仅限于 2 种情况(SS 和 API)
【解决方案2】:

您问题的第一部分由CORS with Postman回答。

对于第二部分,我提出了一个非常小的代码,它利用了短期访问令牌的概念(太简化了)。

// utils/protector.js

let oldToken, newToken;
const checkToken = ({ headers: { 'x-access-token': token } }) =>
  token && (token === oldToken || token === newToken);

const refreshToken = () => {
  oldToken = newToken;
  newToken = Math.random().toString(36).substr(2);
  setTimeout(refreshToken, 3 * 60 * 60 * 1000); // each token is valid for 3 hrs
};
refreshToken();

export { checkToken, newToken as accessToken };
// api/user.js

import { checkToken } from '../../utils/protector';

const handler = (req, res) => {
  // just add this line to the top of your handler
  if (!checkToken(req)) return res.status(403).send();

  // your API logic here...
  res.status(200).json({ name: 'John Doe' });
};

export default handler;

现在在您想使用受保护 API 的页面中:

// index.js

import { useState, useEffect } from 'react';

import { accessToken } from '../utils/protector';

const Home = ({ accessToken }) => {
  const [user, setUser] = useState('Guest');

  useEffect(() => {
    // this will be called from the client
    fetch('/api/user', { headers: { 'x-access-token': accessToken } })
      .then((response) => response.json())
      .then((data) => setUser(data.name));

    return () => {};
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return <div>Hello {user}!</div>;
};

const getServerSideProps = async () => {
  return { props: { accessToken } };
};

export default Home;
export { getServerSideProps };

任何使用受保护路由的页面都应在服务器端呈现,否则保护器代码将与客户端捆绑并在浏览器中执行,从而在服务器端 (API) 和客户端产生不同的令牌。


oldTokennewToken 的概念可能看起来很不清楚。假设:

  • 用户在 11:59 访问您的网站。

  • 您的令牌在 00:00 刷新。

  • 他们在 12:01 触发了 API 调用。

  • 现在,如果没有 oldTokennewToken 在调用之前会发生变化,响应将是 403 Forbidden Error。


我假设用户在您的页面上执行调用受保护 API 的操作之前不到 3 小时。

超时设置为 3 小时,即使在令牌有效时间的两倍之后也是如此,因为如果超时为一半(即 1.5 小时)并且(以上)用户在 01:31 触发 API 调用。那么oldToken 就是newToken,而newToken 是较新的,两者都不会匹配用户所拥有的。

因此,为了保证收到的令牌至少在 3 小时内有效,总生存时间加倍。令牌可能会在 6 小时内有效(例如对于在 12:00 访问该站点的用户)。

【讨论】:

    猜你喜欢
    • 2021-09-10
    • 1970-01-01
    • 1970-01-01
    • 2019-10-28
    • 2014-07-01
    • 2013-08-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多