如何配置 SSO 提供方
页面摘要: 通过在 Strapi 管理面板中配置 SSO 提供方,注册 OAuth/OIDC 应用,将凭据添加到
/config/admin的auth.providers中,并设置回调 URL 以启用额外的登录方式。
(Enterprise 计划) (SSO)
单点登录(SSO) 允许你为 Strapi 管理面板配置额外的登录和注册方式。
- 要在你的应用上配置 SSO,你需要一个 (Enterprise 计划) 套餐或 (SSO) 附加组件。
- 确保 Strapi 是你可以通过你的提供方访问的应用之一。例如,对于 Microsoft (Azure) Active Directory,你必须首先请具有相应权限的人将 Strapi 添加到允许的应用列表中。请参阅你的提供方文档以了解更多相关信息。
- 目前无法将唯一的 SSO 提供方关联到用于 Strapi 账户的电子邮件地址,这意味着对 Strapi 账户的访问不能限制为仅一个 SSO 提供方。有关此问题的更多信息及解决方法,请参阅专门的 GitHub issue。
- 使用 SSO 时,目前无法将管理面板和后端部署在完全不相关、彼此独立的域名上。
访问配置
SSO 配置位于 /config/admin 文件中。
提供方的配置应作为 提供方配置 的数组写入管理面板的 auth.providers 路径中:
JavaScript
module.exports = ({ env }) => ({
// ...
auth: {
providers: [], // 提供方的配置位于此处
},
});
TypeScript
export default ({ env }) => ({
// ...
auth: {
providers: [], // 提供方的配置位于此处
},
});
设置提供方配置
以下文档的某些部分假定此前已在 Strapi 和你的身份提供方中完成某些步骤。如果跳过这些步骤,登录按钮可能会出现在 Strapi 登录页面上,但流程会失败并显示重定向错误或 "invalid client" 错误。在继续阅读文档其余部分之前,请确保遵循清单中的所有步骤。
- 在 Strapi 中启用 SSO 在管理面板中转到 Global settings > Single Sign-On 并配置该功能(例如切换自动注册并选择默认角色)。
- 在你的身份提供方中注册 Strapi 在提供方的仪表板(例如 Azure AD、Okta、Google、GitHub)中,为 Strapi 创建一个新的 OAuth/OIDC 应用。复制提供方生成的客户端 ID 和客户端密钥。
- 将 Strapi 回调 URL 添加到提供方
将提供方配置中的重定向/回调 URL 设置为
{"strapi.admin.services.passport.getStrategyCallbackURL('<provider_uid>')"}生成的值(例如,如果 UID 是google,则为/admin/connect/google)。提供方必须接受此 URL,否则登录将被阻止。 - 向 Strapi 提供凭据
将客户端 ID 和客户端密钥作为环境变量添加(例如
GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET),以便它们可以在{"/config/admin.js|ts"}中读取。 - 在代码中配置提供方
导入提供方的 Passport 策略并将其添加到
auth.providers。 - 重新构建并重启 Strapi
运行
yarn build && yarn develop或npm run build && npm run develop,以便新提供方出现在登录页面上。如果管理面板是单独托管的,还请确保url设置与已部署的管理面板 URL 匹配(参见主机、端口和路径。
提供方的配置是一个使用以下属性构建的 JavaScript 对象:
| Name | Required | Type | Description |
|---|---|---|---|
uid | Yes | String | 策略的 UID。它必须与策略的名称匹配。 |
displayName | Yes | String | 登录页面上用于引用该提供方的名称。 |
createStrategy | Yes | Function | 一个工厂,将构建并返回一个用于你的提供方的新 passport 策略。以 strapi 实例作为参数。 |
icon | No | String | 一个图片 URL。如果指定,它将替换登录页面上的 displayName。 |
uid 属性是每个策略的唯一标识符,通常可以在策略的包中找到。如果你不确定它指的是什么,请联系该策略的维护者。
显示提供方 logo
默认情况下,Strapi 安全策略不允许从外部 URL 加载图片,因此在管理面板的登录屏幕上不会显示提供方 logo,除非通过中间件配置添加安全例外,如下例所示:
JavaScript
module.exports = [
// ...
{
name: 'strapi::security',
config: {
contentSecurityPolicy: {
useDefaults: true,
directives: {
'connect-src': ["'self'", 'https:'],
'img-src': [
"'self'",
'data:',
'blob:',
'dl.airtable.com',
'www.okta.com', // 提供方 logo 的基础 URL
],
'media-src': [
"'self'",
'data:',
'blob:',
'dl.airtable.com',
'www.okta.com', // 提供方 logo 的基础 URL
],
upgradeInsecureRequests: null,
},
},
},
},
// ...
]
TypeScript
export default [
// ...
{
name: 'strapi::security',
config: {
contentSecurityPolicy: {
useDefaults: true,
directives: {
'connect-src': ["'self'", 'https:'],
'img-src': [
"'self'",
'data:',
'blob:',
'dl.airtable.com',
'www.okta.com', // 提供方 logo 的基础 URL
],
'media-src': [
"'self'",
'data:',
'blob:',
'dl.airtable.com',
'www.okta.com', // 提供方 logo 的基础 URL
],
upgradeInsecureRequests: null,
},
},
},
},
// ...
]
设置 cookie 的公共域名
当将管理面板部署到不同位置或不同子域名时,需要进行额外配置以设置 cookie 的公共域名。这是为了确保 cookie 在域之间共享所必需的。
JavaScript
module.exports = ({ env }) => ({
auth: {
domain: env("ADMIN_SSO_DOMAIN", ".test.example.com"),
providers: [
// ...
],
},
url: env("ADMIN_URL", "http://admin.test.example.com"),
// ...
});
TypeScript
export default ({ env }) => ({
auth: {
domain: env("ADMIN_SSO_DOMAIN", ".test.example.com"),
providers: [
// ...
],
},
url: env("ADMIN_URL", "http://admin.test.example.com"),
// ...
});
createStrategy 工厂
passport 策略通常通过使用 2 个参数实例化来构建:配置对象和验证函数。
配置对象
配置对象取决于策略的需求,但通常要求提供一个回调 URL,以便在提供方一侧建立连接后重定向到该 URL。
可以使用 getStrategyCallbackURL 方法为你的提供方生成特定的回调 URL。此 URL 也需要写在提供方一侧,以允许从中重定向。
回调 URL 的格式如下:/admin/connect/<provider_uid>。
strapi.admin.services.passport.getStrategyCallbackURL 是一个 Strapi 辅助函数,可用于获取特定提供方的回调 URL。它以提供方名称作为参数并返回 URL。
如有需要,你还可以在此处放置 OAuth2 应用的客户端 ID 和密钥。
验证函数
验证函数在此处用作中间件,允许你对从提供方 API 返回的数据进行转换和额外处理。
此函数始终以 done 方法作为最后一个参数,该方法用于将所需数据传递给 Strapi 的 SSO 层。
其签名如下:void done(error: any, data: object);,并遵循以下规则:
- 如果
error未设为null,则发送的数据将被忽略,控制器将抛出错误。 - 如果 SSO 的自动注册功能被禁用,则
data对象只需要由email属性组成。 - 如果 SSO 的自动注册功能已启用,则你需要在
data对象中定义(除了email之外)username属性,或同时定义firstname和lastname。
添加提供方
添加新的提供方意味着为你的管理员添加新的登录方式。
Strapi 使用 Passport.js,它支持大量提供方。因此,任何不需要额外自定义数据的有效 passport 策略都应该可以与 Strapi 一起使用。
配置提供方
要配置提供方,请遵循以下流程:
- 确保将你的策略导入到管理面板配置文件中,可以从已安装的包或本地文件导入。
- 向管理面板配置中的
auth.providers数组添加一个与上述格式匹配的新项。 - 使用
yarn build && yarn develop或npm run build && npm run develop重新构建并重启你的应用。该提供方应出现在你的管理面板登录页面上。
提供方配置示例
以下示例展示了如何为最常见的提供方配置 SSO:
- Google — 了解如何配置 Google 与 Strapi 的 SSO。
- GitHub — 了解如何配置 GitHub 与 Strapi 的 SSO。
- Discord — 了解如何配置 Discord 与 Strapi 的 SSO。
- Microsoft — 了解如何配置 Microsoft 与 Strapi 的 SSO。
- Keycloak — 了解如何配置 Keycloak 与 Strapi 的 SSO。
- Okta — 了解如何配置 Okta 与 Strapi 的 SSO。
执行高级自定义
管理面板 URL
如果管理面板所在的主机/端口与 Strapi 服务器不同,则需要更新管理面板 URL:更新/config/admin 文件中的 url 键。
自定义逻辑
在某些场景中,你会希望为连接工作流编写额外的逻辑,例如:
- 限制特定域名的连接和注册
- 在尝试连接时触发操作
- 添加分析
最简单的方法是将代码插入到你的策略的验证函数中。
例如,如果你只想允许使用官方 strapi.io 电子邮件地址的人,可以按如下方式实例化你的策略:
JavaScript
const strategyInstance = new Strategy(configuration, ({ email, username }, done) => {
// 如果电子邮件以 @strapi.io 结尾
if (email.endsWith('@strapi.io')) {
// 则我们使用提供方给出的数据继续
return done(null, { email, username });
}
// 否则,我们通过向 done 函数发送错误来继续
done(new Error('Forbidden email address'));
});
TypeScript
const strategyInstance = new Strategy(configuration, ({ email, username }, done) => {
// 如果电子邮件以 @strapi.io 结尾
if (email.endsWith('@strapi.io')) {
// 则我们使用提供方给出的数据继续
return done(null, { email, username });
}
// 否则,我们通过向 done 函数发送错误来继续
done(new Error('Forbidden email address'));
});
身份验证事件
SSO 功能添加了一个新的身份验证事件:onSSOAutoRegistration。
每当用户使用 SSO 添加的自动注册功能创建时,就会触发此事件。
它包含已创建的用户(event.user)和用于注册提供方(event.provider)。
JavaScript
module.exports = () => ({
auth: {
// ...
events: {
onConnectionSuccess(e) {},
onConnectionError(e) {},
// ...
onSSOAutoRegistration(e) {
const { user, provider } = e;
console.log(
`A new user (${user.id}) has been automatically registered using ${provider}`
);
},
},
},
});
TypeScript
export default () => ({
auth: {
// ...
events: {
onConnectionSuccess(e) {},
onConnectionError(e) {},
// ...
onSSOAutoRegistration(e) {
const { user, provider } = e;
console.log(
`A new user (${user.id}) has been automatically registered using ${provider}`
);
},
},
},
});