Skip to main content

自定义用户与权限插件路由

🌐 Customizing Users & Permissions plugin routes

Page summary:

用户与权限 功能公开了 /users/auth 路由,可以使用插件扩展系统进行扩展或覆盖。本指南展示了如何为用户集合添加自定义策略、覆盖控制器以及添加新路由。

用户与权限功能附带用于身份验证(/auth)和用户管理(/users)的内置路由。因为这些路由属于插件而不是用户创建的内容类型,所以不能使用 createCoreRouter 自定义它们。相反,可以通过在 /src/extensions/users-permissions/ 文件夹中使用 strapi-server 文件,通过 插件扩展系统 扩展它们。

🌐 The Users & Permissions feature ships with built-in routes for authentication (/auth) and user management (/users). Because these routes belong to a plugin rather than a user-created content-type, they cannot be customized with createCoreRouter. Instead, extend them through the plugin extension system using a strapi-server file in the /src/extensions/users-permissions/ folder.

Prerequisites

它是如何运作的

🌐 How it works

用户与权限 使用的路由数组和控制器对象与标准内容类型不同。在自定义它们之前,理解其结构是至关重要的。

路线结构

🌐 Route structure

与你创建的内容类型(例如,api::restaurant.restaurant)不同,Users & Permissions 插件在 plugin.routes['content-api'].routes 数组中注册其路由。该数组包含所有 /users/auth/roles/permissions 路由定义。

🌐 Unlike content-types you create (e.g., api::restaurant.restaurant), the Users & Permissions plugin registers its routes inside the plugin.routes['content-api'].routes array. This array contains all /users, /auth, /roles, and /permissions route definitions.

每条路线都是具有以下结构的对象:

🌐 Each route is an object with the following shape:

{
method: 'GET', // HTTP method
path: '/users', // URL path (relative to /api)
handler: 'user.find', // controller.action
config: {
prefix: '', // path prefix (empty means /api)
},
}

路由配置还可以包含可选的 policiesmiddlewares 数组(参见 添加自定义策略)。

🌐 Route configurations can also include optional policies and middlewares arrays (see Add a custom policy).

strapi-server 扩展文件

🌐 The strapi-server extension file

对“用户与权限”插件的所有自定义都放在一个文件中:

🌐 All customizations to the Users & Permissions plugin go in a single file:

/src/extensions/users-permissions/strapi-server.js
module.exports = (plugin) => {
// Your customizations here

return plugin;
};

该函数接收完整的插件对象并必须返回该插件。在返回之前,你可以修改 plugin.routesplugin.controllersplugin.policiesplugin.services

🌐 The function receives the full plugin object and must return the plugin. You can modify plugin.routes, plugin.controllers, plugin.policies, and plugin.services before returning.

可用操作

🌐 Available actions

user 控制器是一个普通对象,提供以下操作:

🌐 The user controller is a plain object that exposes the following actions:

操作方法路径描述
user.countGET/users/count统计用户
user.findGET/users查找所有用户
user.meGET/users/me获取认证用户
user.findOneGET/users/:id查找单个用户
user.createPOST/users创建用户
user.updatePUT/users/:id更新用户
user.destroyDELETE/users/:id删除用户

auth 控制器是一个工厂函数 ({ strapi }) => ({...}),它暴露了以下操作:

🌐 The auth controller is a factory function ({ strapi }) => ({...}) that exposes the following actions:

操作方法路径限速
auth.callbackPOST/auth/local
auth.callbackGET/auth/:provider/callback
auth.registerPOST/auth/local/register
auth.connectGET/connect/(.*)
auth.forgotPasswordPOST/auth/forgot-password
auth.resetPasswordPOST/auth/reset-password
auth.changePasswordPOST/auth/change-password
auth.emailConfirmationGET/auth/email-confirmation
auth.sendEmailConfirmationPOST/auth/send-email-confirmation
auth.refreshPOST/auth/refresh
auth.logoutPOST/auth/logout
Note

因为 userauth 控制器的类型不同(普通对象 vs. 工厂函数),它们需要不同的重写模式(参见 重写 user 控制器操作重写 auth 控制器操作)。

🌐 Because the user and auth controllers have different types (plain object vs. factory function), they require different override patterns (see Override a user controller action and Override an auth controller action).

自定义路由

🌐 Customize routes

你可以通过修改扩展文件中的 plugin.routes['content-api'].routes 数组来添加策略、注册新端点或移除现有端点。

🌐 You can add policies, register new endpoints, or remove existing ones by modifying the plugin.routes['content-api'].routes array in the extension file.

添加自定义策略

🌐 Add a custom policy

一个常见的要求是限制谁可以更新或删除用户账户:例如,确保用户只能更新自己的资料。

🌐 A common requirement is restricting who can update or delete user accounts: for example, ensuring users can only update their own profile.

1. 创建策略文件

🌐 1. Create the policy file

创建一个全局策略,用于检查身份验证的用户是否与目标用户匹配。策略函数接收 Koa 上下文(可以访问 state.userparams)、一个可选的配置对象,以及 { strapi }

🌐 Create a global policy that checks whether the authenticated user matches the target user. The policy function receives the Koa context (with access to state.user and params), an optional configuration object, and { strapi }:

/src/policies/is-own-user.js
"use strict";

module.exports = (policyContext, config, { strapi }) => {
const currentUser = policyContext.state.user;

if (!currentUser) {
return false;
}

const targetUserId = Number(policyContext.params.id);

if (currentUser.id !== targetUserId) {
return false;
}

return true;
};
Tip

上述 is-own-user 策略专门适用于 Users & Permissions 插件的路由。对于标准内容类型的类似模式(限制访问条目作者),请参见 is-owner 中间件示例is-owner-review 策略示例

🌐 The is-own-user policy above applies specifically to Users & Permissions plugin routes. For a similar pattern on standard content-types (restricting access to the entry author), see the is-owner middleware example and the is-owner-review policy example.

2. 将策略附加到用户路由

🌐 2. Attach the policy to the user routes

在插件扩展文件中,找到 updatedelete 路由并添加策略:

🌐 In the plugin extension file, find the update and delete routes and add the policy:

/src/extensions/users-permissions/strapi-server.js
module.exports = (plugin) => {
// Find the routes that need the policy
const routes = plugin.routes['content-api'].routes;

// Add the 'is-own-user' policy to the update route
const updateRoute = routes.find(
(route) => route.handler === 'user.update'
);

if (updateRoute) {
updateRoute.config = updateRoute.config || {};
updateRoute.config.policies = updateRoute.config.policies || [];
updateRoute.config.policies.push('global::is-own-user');
}

// Add the same policy to the delete route
const deleteRoute = routes.find(
(route) => route.handler === 'user.destroy'
);

if (deleteRoute) {
deleteRoute.config = deleteRoute.config || {};
deleteRoute.config.policies = deleteRoute.config.policies || [];
deleteRoute.config.policies.push('global::is-own-user');
}

return plugin;
};

在此配置下,如果经过身份验证的用户与 URL 中的 :id 不匹配,PUT /api/users/:idDELETE /api/users/:id 会返回 403 Forbidden 错误。

🌐 With this configuration, PUT /api/users/:id and DELETE /api/users/:id return a 403 Forbidden error if the authenticated user does not match the :id in the URL.

Tip

为了获得更有信息量的错误消息,应抛出 PolicyError 而不是返回 false

🌐 For a more informative error message, throw a PolicyError instead of returning false:

const { errors } = require('@strapi/utils');
const { PolicyError } = errors;

// Inside the policy:
throw new PolicyError('You can only modify your own account');

有关策略模式和错误处理的更多详细信息,请参阅 策略文档

添加新路线

🌐 Add a new route

你可以向“用户与权限”插件添加自定义路由。例如,可以按如下方式添加一个停用用户账户的端点:

🌐 You can add custom routes to the Users & Permissions plugin. For example, add an endpoint that deactivates a user account as follows:

/src/extensions/users-permissions/strapi-server.js
module.exports = (plugin) => {
// Add a new controller action
plugin.controllers.user.deactivate = async (ctx) => {
const { id } = ctx.params;

const user = await strapi
.plugin('users-permissions')
.service('user')
.edit(id, { blocked: true });

ctx.body = { message: `User ${user.username} has been deactivated` };
};

// Register the route
plugin.routes['content-api'].routes.push({
method: 'POST',
path: '/users/:id/deactivate',
handler: 'user.deactivate',
config: {
prefix: '',
policies: ['global::is-own-user'],
},
});

return plugin;
};

重启 Strapi 后,POST /api/users/:id/deactivate 将可用。在管理面板的 用户与权限插件 > 角色 中,为需要访问此端点的角色授予相应权限。

删除一条路由

🌐 Remove a route

你可以通过从路由数组中过滤掉某条路由来禁用它。例如,按如下方式禁用用户计数端点:

🌐 You can disable a route by filtering it out of the routes array. For example, disable the user count endpoint as follows:

/src/extensions/users-permissions/strapi-server.js
module.exports = (plugin) => {
plugin.routes['content-api'].routes = plugin.routes['content-api'].routes.filter(
(route) => route.handler !== 'user.count'
);

return plugin;
};

覆盖控制器

🌐 Override controllers

除了路由级别的自定义之外,你还可以覆盖控制器的操作本身,以改变插件处理请求的方式。userauth 控制器使用不同的模式,因此每个都需要特定的方法。

🌐 Beyond route-level customizations, you can override the controller actions themselves to change how the plugin handles requests. The user and auth controllers use different patterns, so each requires a specific approach.

重写 user 控制器操作

🌐 Override a user controller action

user 控制器是一个普通对象,因此你可以直接在扩展文件中读取和替换其方法。例如,要为 me 端点添加自定义逻辑:

🌐 The user controller is a plain object, so you can directly read and replace its methods in the extension file. For instance, to add custom logic to the me endpoint:

/src/extensions/users-permissions/strapi-server.js
module.exports = (plugin) => {
const originalMe = plugin.controllers.user.me;

plugin.controllers.user.me = async (ctx) => {
// Call the original controller
await originalMe(ctx);

// Add extra data to the response
if (ctx.body) {
ctx.body.timestamp = new Date().toISOString();
}
};

return plugin;
};
Caution

在封装控制器时,始终先调用原始函数以保留默认行为。跳过原始函数意味着你将完全接管请求处理,包括数据清理和错误处理。

🌐 When wrapping a controller, always call the original function first to preserve the default behavior. Skipping the original function means you take over the full request handling, including sanitization and error handling.

覆盖 auth 控制器操作

🌐 Override an auth controller action

auth 控制器使用工厂模式:它导出一个函数 ({ strapi }) => ({...}) 而不是一个普通对象。当你的扩展代码运行时,Strapi 尚未解析这个工厂。因此,plugin.controllers.auth 是一个函数,而不是具有方法的对象。

🌐 The auth controller uses a factory pattern: it exports a function ({ strapi }) => ({...}) instead of a plain object. When your extension code runs, Strapi has not yet resolved this factory. As a result, plugin.controllers.auth is a function, not an object with methods.

要覆盖授权操作,请将工厂本身封装起来:

🌐 To override an auth action, wrap the factory itself:

/src/extensions/users-permissions/strapi-server.js
module.exports = (plugin) => {
const originalAuthFactory = plugin.controllers.auth;

plugin.controllers.auth = ({ strapi }) => {
// Resolve the original factory to get the controller methods
const originalAuth = originalAuthFactory({ strapi });

// Override the register method
const originalRegister = originalAuth.register;

originalAuth.register = async (ctx) => {
// Call the original register logic
await originalRegister(ctx);

// Custom post-registration logic
if (ctx.body && ctx.body.user) {
strapi.log.info(`New user registered: ${ctx.body.user.email}`);
}
};

return originalAuth;
};

return plugin;
};
Caution

不要直接访问 plugin.controllers.auth.register。因为 auth 在扩展时是一个工厂函数,它的方法在 Strapi 调用工厂之前是无法访问的。总是像上面示例那样封装工厂。

🌐 Do not access plugin.controllers.auth.register directly. Because auth is a factory function at extension time, its methods are not accessible until Strapi calls the factory. Always wrap the factory as shown above.

完整示例

🌐 Full example

以下示例在单个文件中结合了多种自定义:它向 updatedelete 添加了策略,封装了 me 控制器,并添加了一个新的 profile 路由。

🌐 The following example combines several customizations in a single file: it adds a policy to update and delete, wraps the me controller, and adds a new profile route.

/src/extensions/users-permissions/strapi-server.js
module.exports = (plugin) => {
const routes = plugin.routes['content-api'].routes;

// 1. Add 'is-own-user' policy to update and delete
for (const route of routes) {
if (route.handler === 'user.update' || route.handler === 'user.destroy') {
route.config = route.config || {};
route.config.policies = route.config.policies || [];
route.config.policies.push('global::is-own-user');
}
}

// 2. Wrap the 'me' controller to include the user's role
const originalMe = plugin.controllers.user.me;

plugin.controllers.user.me = async (ctx) => {
await originalMe(ctx);

if (ctx.state.user && ctx.body) {
const user = await strapi
.plugin('users-permissions')
.service('user')
.fetch(ctx.state.user.id, { populate: ['role'] });

ctx.body.role = user.role;
}
};

// 3. Add a custom route
plugin.controllers.user.profile = async (ctx) => {
const user = await strapi
.plugin('users-permissions')
.service('user')
.fetch(ctx.state.user.id, { populate: ['role'] });

ctx.body = {
username: user.username,
email: user.email,
role: user.role?.name,
createdAt: user.createdAt,
};
};

routes.push({
method: 'GET',
path: '/users/profile',
handler: 'user.profile',
config: { prefix: '' },
});

return plugin;
};

验证

🌐 Validation

在进行更改后,重启 Strapi 并验证你的自定义设置:

🌐 After making changes, restart Strapi and verify your customizations:

  1. 运行 yarn strapi routes:list 以确认你的新路由或修改后的路由是否出现。
  2. 在没有身份验证的情况下测试受保护的路由以验证策略返回 403 Forbidden
  3. 使用经过身份验证的用户进行测试以确认预期行为。
  4. 检查 Strapi 服务器启动期间的错误日志。

故障排除

🌐 Troubleshooting

症状可能原因
路由未找到 (404)新路由未推送到 plugin.routes['content-api'].routes,或其 prefix 属性缺失。
策略未应用策略名称不正确。全局策略需要 global:: 前缀(例如,global::is-own-user)。
控制器返回 500控制器操作名称与路由定义中的 handler 值不匹配。
修改未生效修改扩展文件后未重启 Strapi。扩展在启动时加载。
权限被拒绝 (403)新操作未对该角色启用。在 用户和权限插件 > 角色 中启用它。
无法读取 auth 控制器的属性auth 控制器是一个工厂函数,而不是普通对象。应封装工厂函数而不是直接访问方法(参见 覆盖 auth 控制器操作)。