在 Nuxt 3 中,如果任何 REST 端点返回 401,如何导航到登录页面?

问题描述 投票:0回答:1

我正在使用 Nuxt 3 进行服务器端渲染。对于 REST 请求,我使用 Axios。

当用户登录时,刷新令牌 cookie 会保存在浏览器中。然后,对于任何 REST 端点的所有请求,标头中都会包含访问令牌。如果未找到访问令牌,则使用刷新令牌请求一个访问令牌。如果访问令牌已过期,则会请求新的访问令牌,并再次发送失败的请求。所有这些都是由 Axios 拦截器处理的。

现在,我遇到的问题是,如果刷新令牌不再有效,则根本不可能创建新的访问令牌。因此,访问令牌端点返回代码 401。但是当我尝试在 Axios 拦截器中捕获该 401 并导航到登录页面时,它只会在客户端执行此操作。

为了调试该问题,我创建了这个简单的页面。它仍然使用axios,但是没有拦截器。但基本问题似乎是相同的,即服务器端导航不能从承诺链发生。

<template>
  <div>
    {{ data }}
    {{ error }}
  </div>
</template>

<script setup lang="ts">
const userService = useUserService();
const { data, error } = await useAsyncData('data', () => {
  return userService.getSelf().catch(() => {
    try {
      console.log('handling error');
      navigateTo('/login');
      console.log('navigated to /login');
    } catch (ex) {
      console.log(ex);
    }
  })
});
</script>

服务器端日志是这样写的:

handling error

WARN  [nuxt] useAsyncData should return a value that is not null or undefined or the request may be duplicated on the client side.

[nuxt] A composable that requires access to the Nuxt instance was called outside of a plugin, Nuxt hook, Nuxt middleware, or Vue setup function. This is probably not a Nuxt bug. Find out more at https://nuxt.com/docs/guide/concepts/auto-imports#vue-and-nuxt-composables.

很明显,navigateTo 失败并出现异常,因为它从未记录“导航到/登录”。结果,页面被渲染。您基本上无法再访问的页面。

客户端日志这样说:

GET http://127.0.0.1:8080/users/self 401 (Unauthorized)
restricted.vue:13 handling error
restricted.vue:15 navigated to /login

因此,在渲染 /restricted 页面并将请求再次提交到客户端的 REST 端点之后,导航到 /login 成功,并且您将被重定向到 /login

现在,如果我简单地检查数据对象是否已设置,如果没有,则导航到 /login,一切正常。它将针对 /restricted 请求返回状态 302,而不先渲染该页面。正如我所希望的那样。该代码如下所示:

<template> <div> {{ data }} {{ error }} </div> </template> <script setup lang="ts"> const userService = useUserService(); const { data, error } = await useAsyncData('data', () => { return userService.getSelf() }); if (!data.value) { navigateTo('/login') } </script>
当然,这不是处理它的好方法,因为刷新令牌可能在任何给定时刻、在任何 100 个页面上以及在任何 100 个不同的 REST 端点调用期间变得无效。我需要的是对这一切进行某种集中处理。

我错过了什么?有没有更好的方法来处理这样的事情?

axios nuxt.js server-side-rendering nuxt3.js
1个回答
0
投票
我的建议是创建一个自定义可组合项,可以用来代替 Nuxt 的内置 useFetch 或 useAsyncData。在这个可组合项中,您只需将内置的 useFetch 与您的业务逻辑包装在一起即可。 像这样的东西(信用转到

https://github.com/nuxt/nuxt/discussions/16715#discussioncomment-6564177):

// create useCustomFetch to replace useFetch import { withQuery } from 'ufo' import { defu } from 'defu' import type { FetchError } from 'ofetch' import type { AvailableRouterMethod, NitroFetchRequest } from 'nitropack' import type { AsyncData, UseFetchOptions, FetchResult } from 'nuxt/app' type PickFrom<T, K extends Array<string>> = T extends Array<any> ? T : T extends Record<string, any> ? keyof T extends K[number] ? T : K[number] extends never ? T : Pick<T, K[number]> : T; type KeysOf<T> = Array<T extends T ? (keyof T extends string ? keyof T : never) : never>; export function useCustomFetch< ResT = void, ErrorT = FetchError, ReqT extends NitroFetchRequest = NitroFetchRequest, Method extends AvailableRouterMethod<ReqT> = ResT extends void ? 'get' extends AvailableRouterMethod<ReqT> ? 'get' : AvailableRouterMethod<ReqT> : AvailableRouterMethod<ReqT>, _ResT = ResT extends void ? FetchResult<ReqT, Method> : ResT, DataT = _ResT, PickKeys extends KeysOf<DataT> = KeysOf<DataT>, DefaultT = null, >( request: Ref<ReqT> | ReqT | (() => ReqT), opts?: UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>, ): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, ErrorT | null> { const route = useRoute() const router = useRouter() const config = useRuntimeConfig() const { token, logout } = useUserSession() return useFetch( request, defu( <UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>>{ baseURL: config.api.baseUrl, headers: { authorization: `bearer ${token.value}`, }, onRequestError: (error) => { if (error.response?.status === 401) { logout() router.push(withQuery('/auth', { redirect: route.fullPath })) } }, }, opts, ), ) }
您可以修改它以匹配您自己的业务逻辑。请记住,在整个应用程序中,您应该使用此自定义可组合项而不是默认的 useFetch。

© www.soinside.com 2019 - 2024. All rights reserved.