Skip to main content

单点登录

¥Single Sign-On

EnterpriseThis feature is available with an Enterprise plan.

Strapi 上的单点登录允许你为管理面板配置其他登录和注册方法。

¥Single Sign-On on Strapi allows you to configure additional sign-in and sign-up methods for your administration panel.

Prerequisites
  • 需要在 3.5.0 或更高版本上运行的 Strapi 应用。

    ¥A Strapi application running on version 3.5.0 or higher is required.

  • 要在你的应用上配置 SSO,你需要一个 EnterpriseThis feature is available with an Enterprise plan. 计划。

    ¥To configure SSO on your application, you will need an EnterpriseThis feature is available with an Enterprise plan. plan.

  • 确保 SSO 功能为 在管理面板中启用

    ¥Make sure the SSO feature is enabled in the admin panel.

  • 确保 Strapi 是你可以通过提供者访问的应用的一部分。例如,对于 Microsoft (Azure) Active Directory,你必须首先要求具有适当权限的人员将 Strapi 添加到允许的应用列表。请参阅你的提供者文档以了解更多信息。

    ¥Make sure Strapi is part of the applications you can access with your provider. For example, with Microsoft (Azure) Active Directory, you must first ask someone with the right permissions to add Strapi to the list of allowed applications. Please refer to your provider(s) documentation to learn more about that.

提醒

目前无法将唯一的 SSO 提供者与用于 Strapi 账户的电子邮件地址相关联,这意味着对 Strapi 账户的访问不能仅限于一个 SSO 提供者。有关解决此问题的更多信息和解决方法,请参阅专门的 GitHub 问题

¥It is currently not possible to associate a unique SSO provider to an email address used for a Strapi account, meaning that the access to a Strapi account cannot be restricted to only one SSO provider. For more information and workarounds to solve this issue, please refer to the dedicated GitHub issue.

SSO 配置位于应用的服务器配置中,可在 ./config/admin.js 处找到。

¥SSO configuration lives in the server configuration of the application, found at ./config/admin.js.

访问配置

¥Accessing the configuration

提供者的配置应写入管理面板配置的 auth.providers 路径中。

¥The providers' configuration should be written within the auth.providers path of the admin panel configuration.

auth.providers提供者配置 的数组。

¥auth.providers is an array of provider configuration.

./config/admin.js

module.exports = ({ env }) => ({
// ...
auth: {
providers: [], // The providers' configuration lives there
},
});

设置提供者配置

¥Setting up provider configuration

提供者的配置是一个使用以下属性构建的 JavaScript 对象:

¥A provider's configuration is a JavaScript object built with the following properties:

名称必需的类型描述
uidtruestring策略的 UID。它必须与策略名称匹配
displayNametruestring将在登录页面上用于引用提供者的名称
iconfalsestring图片 URL。如果指定,它将替换登录页面上的显示名称
createStrategytruefunction一家将为你的提供者构建并返回新通行证策略的工厂。将 Strapi 实例作为参数
提示

uid 属性是每个策略的唯一标识符,通常可以在策略包中找到。如果你不确定它指的是什么,请联系该策略的维护者。

¥The uid property is the unique identifier of each strategy and is generally found in the strategy's package. If you are not sure of what it refers to, please contact the maintainer of the strategy.

注意

默认情况下,Strapi 安全策略不允许从外部 URL 加载图片,因此提供者徽标不会显示在管理面板的 登录屏幕 上,除非 添加安全例外

¥By default, Strapi security policy does not allow loading images from external URLs, so provider logos will not show up on the login screen of the admin panel unless a security exception is added.

Example: Security exception for provider logos
./config/middlewares.js
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', // Base URL of the provider's logo
],
'media-src': [
"'self'",
'data:',
'blob:',
'dl.airtable.com',
'www.okta.com', // Base URL of the provider's logo
],
upgradeInsecureRequests: null,
},
},
},
},
// ...
]
注意

将管理面板部署到不同位置或不同子域时,需要额外配置来设置 cookie 的公共域。这是确保跨域共享 cookie 所必需的。

¥When deploying the admin panel to a different location or on a different subdomain, an additional configuration is required to set the common domain for the cookies. This is required to ensure the cookies are shared across the domains.

提醒

使用 SSO 时,目前无法在完全不同的不相关域上部署管理和后端。

¥Deploying the admin and backend on entirely different unrelated domains is not possible at this time when using SSO.

Example: Setting custom cookie domain
./config/admin.js
module.exports = ({ env }) => ({
auth: {
domain: env("ADMIN_SSO_DOMAIN", ".test.example.com"),
providers: [
// ...
],
},
url: env("ADMIN_URL", "http://admin.test.example.com"),
// ...
});

createStrategy 工厂

¥The createStrategy Factory

通行证策略通常是通过使用 2 个参数实例化来构建的:配置对象和验证函数。

¥A passport strategy is usually built by instantiating it using 2 parameters: the configuration object, and the verify function.

配置对象

¥Configuration Object

配置对象取决于策略需求,但通常要求在提供者端建立连接后重定向到回调 URL。

¥The configuration object depends on the strategy needs, but often asks for a callback URL to be redirected to once the connection has been made on the provider side.

可以使用 getStrategyCallbackURL 方法为你的提供者生成特定的回调 URL。该 URL 还需要写在提供者端,以便允许从其重定向。

¥A specific callback URL can be generated for your provider using the getStrategyCallbackURL method. This URL also needs to be written on the provider side in order to allow redirection from it.

回调 URL 的格式如下:/admin/connect/<provider_uid>

¥The format of the callback URL is the following: /admin/connect/<provider_uid>.

提示

strapi.admin.services.passport.getStrategyCallbackURL 是一个 Strapi 辅助程序,你可以使用它来获取特定提供者的回调 URL。它采用提供者名称作为参数并返回 URL。

¥strapi.admin.services.passport.getStrategyCallbackURL is a Strapi helper you can use to get a callback URL for a specific provider. It takes a provider name as a parameter and returns a URL.

如果需要,你还可以在此处放置 OAuth2 应用的客户端 ID 和密钥。

¥If needed, this is also where you will put your client ID and secret key for your OAuth2 application.

验证功能

¥Verify Function

验证函数在这里用作中间件,允许用户对从提供者 API 返回的数据进行转换和额外处理。

¥The verify function is used here as a middleware allowing the user to transform and make extra processing on the data returned from the provider API.

该函数始终采用 done 方法作为最后一个参数,用于将所需数据传输到 SSO 的 Strapi 层。

¥This function always takes a done method as last parameter which is used to transfer needed data to the Strapi layer of SSO.

其签名如下:void done(error: any, data: object); 并遵循以下规则:

¥Its signature is the following: void done(error: any, data: object); and it follows the following rules:

  • 如果 error 未设置为 null,则发送的数据将被忽略,控制器将抛出错误。

    ¥If error is not set to null, then the data sent is ignored, and the controller will throw an error.

  • 如果禁用 SSO 的自动注册功能,则 data 对象只需要由 email 属性组成。

    ¥If the SSO's auto-registration feature is disabled, then the data object only need to be composed of an email property.

  • 如果启用了 SSO 的自动注册功能,那么你将需要在 data 对象中定义(除了 email 之外)username 属性或同时定义 firstnamelastname

    ¥If the SSO's auto-registration feature is enabled, then you will need to define (in addition to the email) either a username property or both firstname and lastname within the data object.

添加提供者

¥Adding a provider

添加新的提供者意味着为管理员添加新的登录方式。

¥Adding a new provider means adding a new way for your administrators to log-in.

Strapi 使用 Passport.js,这使得可以选择大量的提供者。因此,任何不需要额外自定义数据的有效通行证策略都应适用于 Strapi。

¥Strapi uses Passport.js, which enables a large selection of providers. Any valid passport strategy that doesn't need additional custom data should therefore work with Strapi.

提醒

诸如 ldapauth 之类的策略不能开箱即用,因为它们需要从管理面板发送额外的数据。如果你想将 LDAP 提供程序添加到你的应用中,你将需要编写 定制策略。你还可以使用 Okta 和 Auth0 等服务作为桥接服务。

¥Strategies such as ldapauth don't work out of the box since they require extra data to be sent from the admin panel. If you want to add an LDAP provider to your application, you will need to write a custom strategy. You can also use services such as Okta and Auth0 as bridge services.

配置提供者

¥Configuring the provider

要配置提供程序,请按照以下步骤操作:

¥To configure a provider, follow the procedure below:

  1. 确保从已安装的软件包或本地文件将策略导入管理配置文件中。

    ¥Make sure to import your strategy in your admin configuration file, either from an installed package or a local file.

  2. 你需要将一个新项目添加到管理面板配置中的 auth.providers 数组中,以匹配 上面给出的格式

    ¥You'll need to add a new item to the auth.providers array in your admin panel configuration that will match the format given above

  3. 重新启动你的应用,提供者应该出现在你的管理员登录页面上。

    ¥Restart your application, the provider should appear on your admin login page.

提供者配置示例

¥Provider configuration examples

谷歌

¥Google

使用:passport-google-oauth2

¥Using: passport-google-oauth2

yarn add passport-google-oauth2
Configuration example for Google:

./config/admin.js



const GoogleStrategy = require("passport-google-oauth2");



module.exports = ({ env }) => ({
auth: {
// ...
providers: [
{
uid: "google",
displayName: "Google",
icon: "https://cdn2.iconfinder.com/data/icons/social-icons-33/128/Google-512.png",
createStrategy: (strapi) =>
new GoogleStrategy(
{
clientID: env("GOOGLE_CLIENT_ID"),
clientSecret: env("GOOGLE_CLIENT_SECRET"),
scope: [
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/userinfo.profile",
],
callbackURL:
strapi.admin.services.passport.getStrategyCallbackURL("google"),
},
(request, accessToken, refreshToken, profile, done) => {
done(null, {
email: profile.email,
firstname: profile.given_name,
lastname: profile.family_name,
});
}
),
},
],
},
});

Github

使用:passport-github

¥Using: passport-github

yarn add passport-github2
Configuration example for Github:
./config/admin.js



const GithubStrategy = require("passport-github2");



module.exports = ({ env }) => ({
auth: {
// ...
providers: [
{
uid: "github",
displayName: "Github",
icon: "https://cdn1.iconfinder.com/data/icons/logotypes/32/github-512.png",
createStrategy: (strapi) =>
new GithubStrategy(
{
clientID: env("GITHUB_CLIENT_ID"),
clientSecret: env("GITHUB_CLIENT_SECRET"),
scope: ["user:email"],
callbackURL:
strapi.admin.services.passport.getStrategyCallbackURL("github"),
},
(accessToken, refreshToken, profile, done) => {
done(null, {
email: profile.emails[0].value,
username: profile.username,
});
}
),
},
],
},
});


Discord

使用:passport-discord

¥Using: passport-discord

yarn add passport-discord
Configuration example for Discord:
./config/admin.js



const DiscordStrategy = require("passport-discord");



module.exports = ({ env }) => ({
auth: {
// ...
providers: [
{
uid: "discord",
displayName: "Discord",
icon: "https://cdn0.iconfinder.com/data/icons/free-social-media-set/24/discord-512.png",
createStrategy: (strapi) =>
new DiscordStrategy(
{
clientID: env("DISCORD_CLIENT_ID"),
clientSecret: env("DISCORD_SECRET"),
callbackURL:
strapi.admin.services.passport.getStrategyCallbackURL(
"discord"
),
scope: ["identify", "email"],
},
(accessToken, refreshToken, profile, done) => {
done(null, {
email: profile.email,
username: `${profile.username}#${profile.discriminator}`,
});
}
),
},
],
},
});

微软

¥Microsoft

使用:passport-azure-ad-oauth2

¥Using: passport-azure-ad-oauth2

yarn add passport-azure-ad-oauth2 jsonwebtoken
Configuration example for Microsoft:
./config/admin.js

const AzureAdOAuth2Strategy = require("passport-azure-ad-oauth2");


const jwt = require("jsonwebtoken");



module.exports = ({ env }) => ({
auth: {
// ...
providers: [
{
uid: "azure_ad_oauth2",
displayName: "Microsoft",
icon: "https://upload.wikimedia.org/wikipedia/commons/thumb/9/96/Microsoft_logo_%282012%29.svg/320px-Microsoft_logo_%282012%29.svg.png",
createStrategy: (strapi) =>
new AzureAdOAuth2Strategy(
{
clientID: env("MICROSOFT_CLIENT_ID", ""),
clientSecret: env("MICROSOFT_CLIENT_SECRET", ""),
scope: ["user:email"],
tenant: env("MICROSOFT_TENANT_ID", ""),
callbackURL:
strapi.admin.services.passport.getStrategyCallbackURL(
"azure_ad_oauth2"
),
},
(accessToken, refreshToken, params, profile, done) => {
let waadProfile = jwt.decode(params.id_token, "", true);
done(null, {
email: waadProfile.email,
username: waadProfile.email,
firstname: waadProfile.given_name, // optional if email and username exist
lastname: waadProfile.family_name, // optional if email and username exist
});
}
),
},
],
},
});

Keycloak(OpenID 连接)

¥Keycloak (OpenID Connect)

使用:passport-keycloak-oauth2-oidc

¥Using: passport-keycloak-oauth2-oidc

yarn add passport-keycloak-oauth2-oidc
Configuration example for Keycloak (OpenID Connect):
./config/admin.js



const KeyCloakStrategy = require("passport-keycloak-oauth2-oidc");



module.exports = ({ env }) => ({
auth: {
// ...
providers: [
{
uid: "keycloak",
displayName: "Keycloak",
icon: "https://raw.githubusercontent.com/keycloak/keycloak-admin-ui/main/themes/keycloak/logo.svg",
createStrategy: (strapi) =>
new KeyCloakStrategy(
{
clientID: env("KEYCLOAK_CLIENT_ID", ""),
realm: env("KEYCLOAK_REALM", ""),
publicClient: env.bool("KEYCLOAK_PUBLIC_CLIENT", false),
clientSecret: env("KEYCLOAK_CLIENT_SECRET", ""),
sslRequired: env("KEYCLOAK_SSL_REQUIRED", "external"),
authServerURL: env("KEYCLOAK_AUTH_SERVER_URL", ""),
callbackURL:
strapi.admin.services.passport.getStrategyCallbackURL(
"keycloak"
),
},
(accessToken, refreshToken, profile, done) => {
done(null, {
email: profile.email,
username: profile.username,
});
}
),
},
],
},
});

奥克塔

¥Okta

使用:passport-okta-oauth20

¥Using: passport-okta-oauth20

yarn add passport-okta-oauth20
提醒

设置 OKTA_DOMAIN 环境变量时,请确保包含协议(例如 https://example.okta.com)。如果不这样做,你将陷入重定向循环。

¥When setting the OKTA_DOMAIN environment variable, make sure to include the protocol (e.g. https://example.okta.com). If you do not, you will end up in a redirect loop.

Configuration example for Okta:
./config/admin.js

const OktaOAuth2Strategy = require("passport-okta-oauth20").Strategy;

module.exports = ({ env }) => ({
auth: {
// ...
providers: [
{
uid: "okta",
displayName: "Okta",
icon: "https://www.okta.com/sites/default/files/Okta_Logo_BrightBlue_Medium-thumbnail.png",
createStrategy: (strapi) =>
new OktaOAuth2Strategy(
{
clientID: env("OKTA_CLIENT_ID"),
clientSecret: env("OKTA_CLIENT_SECRET"),
audience: env("OKTA_DOMAIN"),
scope: ["openid", "email", "profile"],
callbackURL:
strapi.admin.services.passport.getStrategyCallbackURL("okta"),
},
(accessToken, refreshToken, profile, done) => {
done(null, {
email: profile.email,
username: profile.username,
});
}
),
},
],
},
});

执行高级定制

¥Performing advanced customization

管理面板网址

¥Admin panel URL

如果管理面板位于与 Strapi 服务器不同的主机/端口上,则需要更新管理面板 URL:更新 ./config/admin.js 配置文件中的 url 密钥(请参阅 管理面板定制文档)。

¥If the administration panel lives on a host/port different from the Strapi server, the admin panel URL needs to be updated: update the url key in the ./config/admin.js configuration file (see admin panel customization documentation).

自定义逻辑

¥Custom Logic

在某些情况下,你需要为连接工作流程编写额外的逻辑,例如:

¥In some scenarios, you will want to write additional logic for your connection workflow such as:

  • 限制特定域的连接和注册

    ¥restricting connection and registration for a specific domain

  • 连接尝试时触发操作

    ¥triggering actions on connection attempt

  • 添加分析

    ¥adding analytics

最简单的方法是插入策略的验证函数并编写一些代码。

¥The easiest way to do so is to plug into the verify function of your strategy and write some code.

例如,如果你只想允许拥有官方 Strapi.io 电子邮件地址的人员,你可以像这样实例化你的策略:

¥For example, if you want to allow only people with an official strapi.io email address, you can instantiate your strategy like this:

./config/admin.js



const strategyInstance = new Strategy(configuration, ({ email, username }, done) => {


// If the email ends with @strapi.io
if (email.endsWith('@strapi.io')) {
// then we continue with the data given by the provider
return done(null, { email, username });
}

// Otherwise, we continue by sending an error to the done function
done(new Error('Forbidden email address'));
});

认证事件

¥Authentication Events

SSO 功能添加了新的 认证事件onSSOAutoRegistration

¥The SSO feature adds a new authentication event: onSSOAutoRegistration.

每当使用 SSO 添加的自动注册功能创建用户时,都会触发此事件。它包含创建的用户(event.user)和用于注册的提供者(event.provider)。

¥This event is triggered whenever a user is created using the auto-register feature added by SSO. It contains the created user (event.user), and the provider used to make the registration (event.provider).

./config/admin.js

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}`
);
},
},
},
});