创建并添加自定义 Users & Permissions 提供方

页面摘要: 通过在 src/index.js 中注册自定义提供方,并实现返回用户 email 和 username 的 authCallback,为 Strapi 的 Users & Permissions 功能创建自定义 OAuth 提供方,以实现自动注册或登录。

Strapi 为 Users & Permissions 功能 提供了一份内置提供方列表。你也可以按照本指南创建自己的提供方。

WARNING

你已阅读 Users & Permissions 提供方文档 并理解了登录流程。

创建自定义提供方

你可以使用 register 生命周期函数 在 Strapi 应用程序的 src/index.js|ts 文件中创建自己的自定义提供方。请根据你的需要修改以下代码示例:

module.exports = {
  register({ strapi }) {
    strapi
      .plugin("users-permissions")
      .service("providers-registry")
      .add("example-provider-name", {
        icon: "",
        enabled: true,
        grantConfig: {
          key: "",
          secret: "",
          callback: `${strapi.config.server.url}/auth/example-provider-name/callback`,
          scope: ["email"],
          authorize_url: "https://awesome.com/authorize",
          access_url: "https://awesome.com/token",
          oauth: 2,
        },
        async authCallback({ accessToken, providers, purest }) {
          // use whatever you want here to get the user info
          return {
            username: "test",
            email: "test",
          };
        },
      });
  },
};

有关传递给 grantConfig 的参数的更多信息,请参阅 grant 文档。有关 purest 的更多信息,请参阅 purest 文档。

身份验证流程的工作原理

authCallback 函数返回的对象必须至少包含 username 和 email 属性。Strapi 使用返回的邮箱地址自动解析注册和登录的身份验证流程:

  • 如果用户不存在:Strapi 使用返回的 email 和 username 注册一个新用户,然后登录该用户,并返回 JWT 和用户对象。
  • 如果用户已存在:Strapi 检索与该 email 匹配的用户并登录,返回 JWT 和用户对象。

由于这种设计,你无需在自定义提供方中实现单独的查找或注册逻辑。你只需确保 authCallback 从外部提供方获取用户的档案,并返回用户的 email 和 username。

前端设置

配置好 Strapi 和提供方后,在你的前端应用中你必须:

  • 创建一个链接到 GET STRAPI_BACKEND_URL/api/connect/${provider} 的按钮(例如 https://strapi.mywebsite/api/connect/github)。

  • 创建一个类似 FRONTEND_URL/connect/${provider}/redirect 的前端路由,用于处理 access_token 参数,并向 STRAPI_BACKEND_URL/api/auth/${provider}/callback 发送带有 access_token 参数的请求。

    该 JSON 请求响应将为 { "jwt": "...", "user": {...} }。

现在你可以发起经过身份验证的请求,如令牌用法中所述。

故障排查
  • 未提供邮箱:当 authCallback 未能返回有效的 email 地址时会出现此错误。
    • 确保你的提供方应用配置了正确的 OAuth 范围(例如 email 或 profile)来请求用户的邮箱。
    • 检查身份提供者是否确实在载荷中返回了邮箱。请注意,部分用户可能在身份提供者一侧将其邮箱地址隐藏或设为私有。
  • 错误 429:很可能是因为你的登录流程陷入了循环。要向后端发起新请求,你需要等待几分钟或重启后端。
  • Grant:缺少会话或提供方配置错误:可能由多种原因导致。
    • 无法构建重定向 URL:确保你已在 config/server.js 中设置了后端 URL:设置服务器 URL
    • 会话/Cookie/缓存问题:你可以在隐私窗口中重试。
    • 错误地使用了 ngrok 域名:检查你的 URL,确保使用 ngrok URL 而不是 http://localhost:1337。别忘了检查示例应用在 src/config.js 中设置的后端 URL。
  • 你无法访问管理面板:很可能是因为你构建管理面板时后端 URL 设置为 ngrok URL,而你停止/重启了 ngrok。你需要将后端 URL 替换为新的 ngrok URL,并重新运行 yarn build 或 npm run build。

重置密码

仅适用于使用邮箱提供方注册的用户。

假设的一般流程:

  1. 用户访问你的忘记密码页面。
  2. 用户输入他们的邮箱地址。
  3. 你的忘记密码页面向后端发送请求,将包含重置密码链接的邮件发送给用户。
  4. 用户收到邮件并点击该特殊链接。
  5. 该链接将用户重定向到你的重置密码页面。
  6. 用户输入他们的新密码。
  7. 重置密码页面向后端发送包含新密码的请求。
  8. 如果请求包含第 3 步链接中的 code,则密码会被更新。
  9. 用户可以使用新密码登录。

以下部分详细说明第 3 步和第 7 步。

忘记密码:请求重置密码链接

此操作会向用户发送一封包含指向你重置密码页面的链接的邮件。该链接会附加 URL 参数 code,这是第 7 步重置密码所需要的。

首先,你必须指定以下内容:

  • 在管理面板中:Settings > USERS & PERMISSIONS PLUGIN > Advanced Settings > Reset Password 页面,设置指向你重置密码页面的 url。
  • 在管理面板中:Settings > USERS & PERMISSIONS PLUGIN > Email Template 页面,设置 Shipper email(发件人邮箱)。

然后,你的忘记密码页面必须向后端发起以下请求:

import axios from 'axios';

// Request API.
axios
  .post('http://localhost:1337/api/auth/forgot-password', {
    email: 'user@strapi.io', // user's email
  })
  .then(response => {
    console.log('Your user received an email');
  })
  .catch(error => {
    console.log('An error occurred:', error.response);
  });

重置密码:发送新密码

此操作将更新用户密码。 这也适用于 GraphQL 插件,使用 resetPassword 变更。

你的重置密码页面必须向后端发起以下请求:

import axios from 'axios';

// Request API.
axios
  .post('http://localhost:1337/api/auth/reset-password', {
    code: 'privateCode', // code contained in the reset link of step 3.
    password: 'userNewPassword',
    passwordConfirmation: 'userNewPassword',
  })
  .then(response => {
    console.log("Your user's password has been reset.");
  })
  .catch(error => {
    console.log('An error occurred:', error.response);
  });

你也可以通过 /change-password API 端点更新已认证用户的密码:

import axios from 'axios';

// Request API.
axios.post(
  'http://localhost:1337/api/auth/change-password',
  {
    currentPassword: 'currentPassword',
    password: 'userNewPassword',
    passwordConfirmation: 'userNewPassword',
  },
  {
    headers: {
      Authorization: 'Bearer <user jwt>',
    },
  }
);

邮箱验证

NOTE

在生产环境中,请确保设置了 url 配置属性。否则验证链接会重定向到 localhost。有关该配置的更多信息,请参阅此处。

注册后,如果你将 Enable email confirmation(启用邮箱确认)设置为 ON(开启),用户将通过邮件收到一个确认链接。用户必须点击该链接来验证其注册。

如果需要,你可以通过发起以下请求来重新发送确认邮件:

import axios from 'axios';

// Request API.
axios
  .post(`http://localhost:1337/api/auth/send-email-confirmation`, {
    email: 'user@strapi.io', // user's email
  })
  .then(response => {
    console.log('Your user received an email');
  })
  .catch(error => {
    console.error('An error occurred:', error.response);
  });

向你的 Strapi 应用程序添加新提供方

INFO

本文档可能尚未与 Strapi 5 保持同步,且仍在完善中。同时,非常欢迎 贡献。

Grant 为许多常用的 OAuth 提供方提供了配置。自定义 提供方同样受支持。 你可以在此处查看并尝试 200 多个受支持的提供方:OAuth Playground。

准备你的文件

要在 Strapi 上添加新提供方,你需要对以下文件进行修改:

extensions/users-permissions/services/Providers.js
extensions/users-permissions/config/functions/bootstrap.js

如果这些文件不存在,你需要从 node_modules 或 Strapi monorepo 中复制。有关其工作原理的更多信息,请参阅插件扩展。

我们将逐步进行。

配置你的提供方请求

在 Provider.js 文件的 getProfile 函数中配置新提供方。

getProfile 接受三个参数:

  • provider:所用提供方的名称(字符串形式)。
  • query:查询是提供方回调的结果。
  • callback:用于继续 Strapi 内部登录逻辑的回调函数。

以下是一个使用 discord 提供方的示例。

配置你的 OAuth 通用信息

case 'discord': {
  const discord = new Purest({
    provider: 'discord',
    config: {
      'discord': {
        'https://discordapp.com/api/': {
          '__domain': {
            'auth': {
              'auth': {'bearer': '[0]'}
            }
          },
          '{endpoint}': {
            '__path': {
              'alias': '__default'
            }
          }
        }
      }
    }
  });
}

这段代码创建了一个 Purest 对象,为我们提供了一种与提供方 REST API 交互的通用方式。

有关使用 Purest 模块的更多说明,请参阅 Purest 官方文档

你也可以查看许多已有的配置 此处。

获取用户信息

我们的 Discord 提供方示例如下:

  discord.query().get('users/@me').auth(access_token).request((err, res, body) => {
    if (err) {
      callback(err);
    } else {
      // Combine username and discriminator because discord username is not unique
      const username = `${body.username}#${body.discriminator}`;
      callback(null, {
        username,
        email: body.email
      });
    }
  });
  break;
}

这是我们 switch 语句的下一部分。现在我们已经正确配置了提供方,想要用它来获取用户信息。

这里可以看到 purest 的真正威力:你可以在目标 URL 上简单地发起一个 get 请求,使用 query 参数中的 access_token 进行身份验证。

这样,你应该能够获取所需的用户信息。

现在,你只需使用用户的 username 和 email 调用 callback 函数即可。这样,Strapi 就能从数据库中检索你的用户并让你登录。

将新提供方模型配置到数据库

现在,我们需要为新提供方配置"model"(模型)。这样,我们的设置就可以存储在数据库中,并从管理面板进行管理。

打开文件 packages/strapi-plugin-users-permissions/config/functions/bootstrap.js

将你的提供方所需的字段添加到 grantConfig 对象中。 我们的 discord 提供方示例如下:

discord: {
  enabled: false,  // make this provider disabled by default
  icon: 'comments', // The icon to use on the UI
  key: '',  // our provider app id (leave it blank, you will fill it with the Content Manager)
  secret: '', // our provider secret key (leave it blank, you will fill it with the Content Manager)
  callback: '/auth/discord/callback', // the callback endpoint of our provider
  scope: [  // the scope that we need from our user to retrieve information
    'identify',
    'email'
  ]
},