创建并添加自定义 Users & Permissions 提供方
页面摘要: 通过在
src/index.js中注册自定义提供方,并实现返回用户username的authCallback,为 Strapi 的 Users & Permissions 功能创建自定义 OAuth 提供方,以实现自动注册或登录。
Strapi 为 Users & Permissions 功能 提供了一份内置提供方列表。你也可以按照本指南创建自己的提供方。
你已阅读 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)来请求用户的邮箱。 - 检查身份提供者是否确实在载荷中返回了邮箱。请注意,部分用户可能在身份提供者一侧将其邮箱地址隐藏或设为私有。
- 确保你的提供方应用配置了正确的 OAuth 范围(例如
- 错误 429:很可能是因为你的登录流程陷入了循环。要向后端发起新请求,你需要等待几分钟或重启后端。
- Grant:缺少会话或提供方配置错误:可能由多种原因导致。
- 无法构建重定向 URL:确保你已在
config/server.js中设置了后端 URL:设置服务器 URL - 会话/Cookie/缓存问题:你可以在隐私窗口中重试。
- 错误地使用了 ngrok 域名:检查你的 URL,确保使用 ngrok URL 而不是
http://localhost:1337。别忘了检查示例应用在src/config.js中设置的后端 URL。
- 无法构建重定向 URL:确保你已在
- 你无法访问管理面板:很可能是因为你构建管理面板时后端 URL 设置为 ngrok URL,而你停止/重启了 ngrok。你需要将后端 URL 替换为新的 ngrok URL,并重新运行
yarn build或npm run build。
重置密码
仅适用于使用邮箱提供方注册的用户。
假设的一般流程:
- 用户访问你的忘记密码页面。
- 用户输入他们的邮箱地址。
- 你的忘记密码页面向后端发送请求,将包含重置密码链接的邮件发送给用户。
- 用户收到邮件并点击该特殊链接。
- 该链接将用户重定向到你的重置密码页面。
- 用户输入他们的新密码。
- 重置密码页面向后端发送包含新密码的请求。
- 如果请求包含第 3 步链接中的 code,则密码会被更新。
- 用户可以使用新密码登录。
以下部分详细说明第 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>',
},
}
);
邮箱验证
在生产环境中,请确保设置了 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 应用程序添加新提供方
本文档可能尚未与 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'
]
},