# 使用 JWT 的身份验证流程

> Source: https://strapi.nodejs.cn/cms/backend-customization/examples/authentication

🌐 Examples cookbook: Authentication flow with JWT

使用 JWT 验证 REST API 请求，通过将凭证发送到 `/auth/local` 端点并将令牌存储在 `localStorage`，可选的会话管理支持刷新令牌。

🌐 Authenticate REST API requests using JWT by sending credentials to the `/auth/local` endpoint and storing the token in `localStorage`, with optional session management for refresh token support.

:::prerequisites

此页面是后端自定义示例手册的一部分。请确保你已阅读其[介绍](/cms/backend-customization/examples)。

🌐 This page is part of the back end customization examples cookbook. Please ensure you've read its [introduction](/cms/backend-customization/examples).

:::

**💭 上下文:**

开箱即用， [FoodAdvisor](https://github.com/strapi/foodadvisor) 的前端网站没有提供任何登录功能。登录是通过访问 Strapi 的管理面板完成的，地址为 [`localhost:1337/admin`](http://localhost:1337/admin`)。

<SideBySideContainer>

让我们在前端添加一个基本的登录页面，该前端是由 [Next.js](https://next.nodejs.cn/)驱动的网站，包含在 FoodAdvisor 的 `/client` 文件夹中。登录页面可以通过 [`localhost:3000/auth/login`](http://localhost:3000/auth/login) 访问，并包含一个典型的邮箱/密码登录表单。这将允许以编程方式验证发送到 Strapi 的 API 请求。

<SideBySideColumn>

<figure style={{ width: '100%', margin: '0' }}>
  <img src="/img/assets/backend-customization/tutorial-auth-flow.png" alt="Example login page" />
  <em><figcaption style={{ fontSize: '12px' }}>FoodAdvisor 前端网站上登录表单的一个可能示例</figcaption></em>
</figure>

**🎯 目标**:

创建前端组件以：

🌐 Create a front-end component to:

1. 显示登录表单，
2. 向 Strapi 后端服务器的 `/auth/local` 路由发送请求以进行身份验证，
3. 获取一个 [JSON Web Token](https://en.wikipedia.org/wiki/JSON_Web_Token) (JWT)，
4. 并将 JWT 存储到浏览器的 [`localStorage`](https://web.nodejs.cn/en-US/docs/Web/API/Window/localStorage) 属性中，以便以后检索和认证我们的请求。

**相关概念**

有关 JWT 认证的更多信息可以在 [用户与权限插件](/cms/features/users-permissions) 文档中找到。

🌐 Additional information about JWT authentication can be found in the [Users & Permissions plugin](/cms/features/users-permissions) documentation.

**🧑‍💻 代码示例：**

为了实现这一点，在 [FoodAdvisor](https://github.com/strapi/foodadvisor) 项目的`/client`文件夹中，你可以创建一个包含以下示例代码的`pages/auth/login.js`文件。高亮的行显示了发送到 Strapi 的用户与权限插件提供的`/auth/local`路由的请求：

此文件使用了 formik 包 - 使用 `yarn add formik` 安装它并重启开发服务器。

🌐 This file uses the formik package - install it using `yarn add formik` and restart the dev server.

```jsx title="/client/pages/auth/login.js" {21-27}

const Login = () => {
  const { handleSubmit, handleChange } = useFormik({
    initialValues: {
      identifier: '',
      password: '',
    },
    onSubmit: async (values) => {
      /**
       * API URLs in Strapi are by default prefixed with /api,
       * but because the API prefix can be configured
       * with the rest.prefix property in the config/api.js file,
       * we use the getStrapiURL() method to build the proper full auth URL.
       **/
      const res = await fetch(getStrapiURL('/auth/local'), {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(values),
      });
      /**
       * Gets the JWT from the server response.
       * The actual response is { jwt, user }, but we only need the JWT here.
       */
      const { jwt } = await res.json();
      /**
       * Stores the JWT in the localStorage of the browser. 
       * A better implementation would be to do this with an authentication context provider
       * or something more sophisticated, but it's not the purpose of this tutorial.
       */
      localStorage.setItem('token', jwt); 
    },
  });
  /**
   * The following code renders a basic login form 
   * accessible from the localhost:3000/auth/login page.
   */
  return (
    <Layout>
      <div className="h-full w-full flex justify-center items-center my-24">
        <form onSubmit={handleSubmit} className="flex flex-col gap-y-6 w-4/12 ">
          <h1 className="font-bold text-3xl mb-6">Login</h1>

          Login
        </form>
      </div>
    
  );
};

```

## 增强认证与会话管理 {#enhanced-authentication-with-session-management}

🌐 Enhanced authentication with session management

上面的示例使用了传统的 JWT 方法。为了增强安全性，你可以在“用户与权限”配置中启用会话管理模式，该模式提供生命周期更短的访问令牌和刷新令牌功能。

🌐 The above example uses the traditional JWT approach. For enhanced security, you can enable session management mode in your Users & Permissions configuration, which provides shorter-lived access tokens and refresh token functionality.

### 配置 {#configuration}

🌐 Configuration

首先，在你的 `/config/plugins.js` 中启用会话管理：

🌐 First, enable session management in your `/config/plugins.js`:

```js title="/config/plugins.js"
module.exports = ({ env }) => ({
  'users-permissions': {
    config: {
      jwtManagement: 'refresh',
      sessions: {
        accessTokenLifespan: 600, // 10 minutes (default)
        maxRefreshTokenLifespan: 2592000, // 30 days (default)
        idleRefreshTokenLifespan: 1209600, // 14 days (default)
        maxSessionLifespan: 86400, // 1 day (default)
        idleSessionLifespan: 7200, // 2 hours (default)
      },
    },
  },
});
```

### 增强的登录组件 {#enhanced-login-component}

🌐 Enhanced login component

这是一个更新的登录组件，可以同时处理 JWT 和刷新令牌：

🌐 Here's an updated login component that handles both JWT and refresh tokens:

```jsx title="/client/pages/auth/enhanced-login.js"

const EnhancedLogin = () => {
  const [isLoading, setIsLoading] = useState(false);

  const { handleSubmit, handleChange } = useFormik({
    initialValues: {
      identifier: '',
      password: '',
    },
    onSubmit: async (values) => {
      setIsLoading(true);
      try {
        const res = await fetch(getStrapiURL('/auth/local'), {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
          },
          body: JSON.stringify(values),
        });

        const data = await res.json();

        if (res.ok) {
          // Store both tokens (session management mode)
          if (data.refreshToken) {
            localStorage.setItem('accessToken', data.jwt);
            localStorage.setItem('refreshToken', data.refreshToken);
          } else {
            // Legacy mode - single JWT
            localStorage.setItem('token', data.jwt);
          }

          // Redirect to protected area
          window.location.href = '/dashboard';
        } else {
          console.error('Login failed:', data.error);
        }
      } catch (error) {
        console.error('Login error:', error);
      } finally {
        setIsLoading(false);
      }
    },
  });

  return (
    <Layout>
      <div className="h-full w-full flex justify-center items-center my-24">
        <form onSubmit={handleSubmit} className="flex flex-col gap-y-6 w-4/12">
          <h1 className="font-bold text-3xl mb-6">Enhanced Login</h1>

          {isLoading ? 'Logging in...' : 'Login'}
        </form>
      </div>
    
  );
};

```

<br />

:::strapi What's next?

了解更多关于自定义[服务和控制器](/cms/backend-customization/examples/services-and-controllers)如何帮助你调整基于 Strapi 的应用的信息。

🌐 Learn more about how custom [services and controllers](/cms/backend-customization/examples/services-and-controllers) can help you tweak a Strapi-based application.

:::
