关于jwt:如何在Next.js中实现身份验证

How to implement authentication in Next.js

我是Next.js的新手,并且正在尝试使用jwt令牌进行身份验证系统。我想知道什么是最好的/标准的方法来存储jwt令牌和使用身份验证系统进行路由。我一直在尝试来自不同教程/文章的不同方法,但不太了解。这是我尝试过的。

  • 用户登录时,它将用户名/密码发送到单独的API服务器(例如,处理后端内容的新项目),服务器将以access-token响应,然后在Next.js项目中,我将cookie设置为收到令牌。在Next.js项目中,受保护的路由将用withAuth hoc包装,该hoc将检查cookie中的令牌。这种方法的问题在于,由于cookie没有httpOnly标志,因此XSS容易受到攻击。

  • 这类似于1.),但是使用localStorage,问题是access-token不能在第一个请求上发送到服务器。 (我不确定这个,但是据我理解,在每个HTTP请求中,我必须手动粘贴我的access-token,所以对于我无法控制的请求该怎么办?例如第一个请求或使用标签)。

  • 我在Next.js服务器(自定义快递服务器)中编写了身份验证后端。当用户登录时,服务器将对其进行验证,然后设置一个httpOnly cookie。然后的问题是,利用客户端路由(使用Next.js路由器转到URL),它无法检查令牌。例如,如果页面用withAuth hoc包裹,但无法使用javascript访问cookie内的令牌。

  • 我见过很多人,在受保护路由的getInitialProps中,他们只检查cookie / localStorage中的存在令牌,然后如果令牌被吊销或列入黑名单该怎么办,他们如何处理它,因为他们没有将令牌发送到服务器?还是在每个客户端页面更改时都必须将令牌发送到服务器?


    由于我们正在隔离,所以我有足够的时间来回答这个问题。这将是一个很长的答案。

    Next.js使用App组件初始化页面。 _app页面负责呈现我们的页面。我们在_app.js上对用户进行身份验证,因为我们从getInitialProps返回的任何内容都可以被所有其他页面访问。我们在这里对用户进行身份验证,身份验证决定将传递到页面,从页面到页眉,因此每个页面都可以确定用户是否已通过身份验证。 (请注意,可以使用redux来完成,而无需进行支撑钻孔,但这会使答案更加复杂)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
      static async getInitialProps({ Component, router, ctx }) {
        let pageProps = {};
        const user = process.browser
          ? await auth0.clientAuth()
          : await auth0.serverAuth(ctx.req); // I explain down below

        //this will be sent to all the components
        const auth = { user, isAuthenticated: !!user };
        if (Component.getInitialProps) {
          pageProps = await Component.getInitialProps(ctx);
        }

        return { pageProps, auth };
      }

      render() {
        const { Component, pageProps, auth } = this.props;
        return <Component {...pageProps} auth={auth} />;
      }
    }

    如果我们在浏览器上并且需要检查用户是否已通过身份验证,我们只需从浏览器中检索cookie,这很容易。但是我们总是必须验证令牌。这与浏览器和服务器使用的过程相同。我将在下面解释。但是,如果我们在服务器上。我们无权访问浏览器中的cookie。但是我们可以从" req"对象中读取内容,因为cookie附加到了req.header.cookie。这就是我们访问服务器上Cookie的方式。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    async serverAuth(req) {
        // console.log(req.headers.cookie) to check
        if (req.headers.cookie) {
          const token = getCookieFromReq(req,"jwt");
          const verifiedToken = await this.verifyToken(token);
          return verifiedToken;
        }
        return undefined;
      }

    这是getCookieFromReq()。请记住,我们必须考虑功能性。

    1
    2
    3
    4
    5
    6
    7
    8
    const getCookieFromReq = (req, cookieKey) => {
      const cookie = req.headers.cookie
        .split(";")
        .find((c) => c.trim().startsWith(`${cookieKey}=`));

      if (!cookie) return undefined;
      return cookie.split("=")[1];
    };

    获得Cookie后,我们必须对其进行解码,提取到期时间以查看其是否有效。这部分很容易。我们必须检查的另一件事是jwt的签名是否有效。对称或非对称算法用于对jwt进行签名。您必须使用私钥来验证对称算法的签名。 RS256是API的默认非对称算法。使用RS256的服务器为您提供了一个链接,以使jwt使用密钥来验证签名。您可以使用[jwks-rsa] [1],也可以自行执行。您必须创建一个证书,然后验证令牌是否有效。

    假设我们的用户现在已通过身份验证。您说:"而且我看到很多人在受保护路线的getInitialProps中只检查cookie / localStorage中的存在令牌。"我们使用受保护的路由仅将访问权限授予授权用户。为了访问这些路由,用户必须显示其jwt令牌,express.js使用中间件检查用户的令牌是否有效。由于您已经看到了很多示例,因此我将跳过这一部分。

    "然后,如果令牌被吊销或列入黑名单,又怎么办呢,因为它们没有将令牌发送到服务器?或者我必须在每次更改客户端页面时都将令牌发送到服务器?"

    通过验证令牌过程,我们可以100%确定令牌是否有效。当客户端要求服务器访问某些机密数据时,客户端必须将令牌发送到服务器。想象一下,当您安装组件时,组件会要求服务器从受保护的路由中获取一些数据。服务器将提取req对象,获取jwt并将其用于从受保护的路由中获取数据。为浏览器和服务器获取数据的实现方式不同。如果浏览器发出请求,则只需要相对路径,而服务器则需要绝对路径。如您所知,获取数据是通过组件的getInitialProps()完成的,并且此函数在客户端和服务器上均执行。这是您应如何实施。我只是附加了getInitialProps()部分。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    MyComponent.getInitialProps = async (ctx) => {
      const another = await getSecretData(ctx.req);
     //reuslt of fetching data is passed to component as props
      return { superValue: another };
    };



        const getCookieFromReq = (req, cookieKey) => {
          const cookie = req.headers.cookie
            .split(";")
            .find((c) => c.trim().startsWith(`${cookieKey}=`));

          if (!cookie) return undefined;
          return cookie.split("=")[1];
        };

       
        const setAuthHeader = (req) => {
          const token = req ? getCookieFromReq(req,"jwt") : Cookies.getJSON("jwt");

          if (token) {
            return {
              headers: { authorization: `Bearer ${token}` },
            };
          }
          return undefined;
        };

       
        export const getSecretData = async (req) => {
          const url = req ?"http://localhost:3000/api/v1/secret" :"/api/v1/secret";
          return await axios.get(url, setAuthHeader(req)).then((res) => res.data);
        };



      [1]: https://www.npmjs.com/package/jwks-rsa


    随着Next.JS v8的引入,在NextJS示例页面中放置了一些示例。要遵循的基本思想是:

    JWT

    • 使用Cookie存储令牌(您可以选择是否进一步加密令牌)
    • 发送cookie作为授权标头

    OAuth

    • 使用第三方身份验证服务,例如OAuth2.0
    • 使用护照