Skip to main content

服务器 API:策略和中间件

🌐 Server API: Policies & middlewares

Page summary:

就像 Strapi 核心一样,插件也可以有策略和中间件。插件策略在控制器动作之前运行,并返回 truefalse 来允许或阻止请求。插件中间件在整个请求/响应周期中按顺序运行,并调用 next() 以继续。将策略和中间件声明为工厂函数的对象,并在路由中通过插件命名空间的名称引用它们。

策略和中间件是插件服务器中拦截请求的两种机制。策略决定请求是否应该继续。中间件决定它如何被处理。

🌐 Policies and middlewares are the two mechanisms for intercepting requests in a plugin server. Policies decide whether a request should proceed. Middlewares shape how it is processed.

Prerequisites

在深入了解本页的概念之前,请确保你已经:

🌐 Before diving deeper into the concepts on this page, please ensure you have:

决策指南

🌐 Decision guide

在编写任何代码之前,请使用此表选择正确的机制:

🌐 Use this table to pick the right mechanism before writing any code: | 需求 | 机制 || --- | --- || 根据用户角色或状态阻止请求 | 策略 || 根据请求内容(body, headers)阻止请求 | 策略 或路由配置中的内联策略 || 当条件不满足时返回 403 | 策略 || 在多个路由间重用相同访问规则 | 命名 策略(已注册并通过名称引用) || 为插件的路由的每个响应添加头信息 | 路由级中间件 || 在整个服务器上记录或跟踪每个请求 | 服务器级中间件 (strapi.server.use()) || 在到达控制器之前修改 ctx.query | 路由级中间件 || 在多个路由间共享逻辑 | 命名 路由级中间件(已注册并通过名称引用) |

政策

🌐 Policies

策略是一个在给定路由的控制器操作之前运行的函数。它接收请求上下文,评估一个条件,并返回 true 以允许请求,或者返回 false(或抛出异常)以通过 403 响应阻止请求。

🌐 A policy is a function that runs before the controller action for a given route. It receives the request context, evaluates a condition, and returns true to allow the request or false (or throws) to block it with a 403 response.

声明

🌐 Declaration

策略以普通函数的形式导出(不是工厂函数)。每个策略接收以下三个参数:

🌐 Policies are exported as plain functions (not factory functions). Each policy receives the following 3 arguments:

  • policyContext 是 Koa 上下文对象的一个封装。使用它可以访问 policyContext.state.userpolicyContext.request 等。
  • config 包含在附加策略时传入的每条路由配置(例如,{ name: 'plugin::my-plugin.hasRole', options: { role: 'editor' } },其中 config 是每策略的选项对象)。
  • { strapi } 提供对 Strapi 实例的访问。
Note

policyContext.state.user 的确切形状取决于认证上下文(例如,管理员面板认证与用户与权限 / 内容 API 认证)。请将角色查找逻辑调整为适应你的项目。

🌐 The exact shape of policyContext.state.user depends on the authentication context (for example, admin panel authentication vs. Users & Permissions / Content API authentication). Adapt the role lookup logic to your project.

/src/plugins/my-plugin/server/src/policies/index.js
'use strict';

const hasRole = require('./has-role');

module.exports = {
hasRole,
};
/src/plugins/my-plugin/server/src/policies/has-role.js
'use strict';

// Allow the request only if the user has the role specified in the route config
// Usage in route: { name: 'plugin::my-plugin.hasRole', options: { role: 'editor' } }
module.exports = (policyContext, config, { strapi }) => {
const { user } = policyContext.state;
const targetRole = config.role;

if (!user || !targetRole) {
return false;
}

// Supports both `user.role` and `user.roles` shapes depending on auth strategy.
const roles = Array.isArray(user.roles)
? user.roles
: user.role
? [user.role]
: [];

return roles.some((role) => {
if (typeof role === 'string') return role === targetRole;
return role?.code === targetRole || role?.name === targetRole;
});
};

在路由中的使用

🌐 Usage in routes

一旦声明,可以使用 plugin::my-plugin.policy-name 命名空间从路由中引用插件策略:

🌐 Once declared, reference a plugin policy from a route using the plugin::my-plugin.policy-name namespace:

/src/plugins/my-plugin/server/src/routes/index.js
'use strict';

module.exports = [
{
method: 'GET',
path: '/dashboard',
handler: 'dashboard.find',
config: {
policies: ['plugin::my-plugin.isActive'], // simple reference by namespaced name
},
},
{
method: 'DELETE',
path: '/articles/:id',
handler: 'article.delete',
config: {
policies: [{ name: 'plugin::my-plugin.hasRole', options: { role: 'editor' } }], // with per-route config
},
},
{
method: 'GET',
path: '/public',
handler: 'article.findAll',
config: {
policies: [(policyContext, config, { strapi }) => true], // inline policy, no registration needed
},
},
];
Policy return values

返回 false 会导致 Strapi 发送 403 Forbidden 响应。返回空 (undefined) 被视为允许(允许),而不是阻止。始终明确返回 truefalse。抛出错误会导致 Strapi 发送 500 响应,除非你抛出 Strapi HTTP 错误类(例如 new errors.PolicyError(...)new errors.ForbiddenError(...)new errors.UnauthorizedError(...))。

🌐 Returning false causes Strapi to send a 403 Forbidden response. Returning nothing (undefined) is treated as permissive (allowed), not as a block. Always return true or false explicitly. Throwing an error causes Strapi to send a 500 response unless you throw a Strapi HTTP error class (e.g., new errors.PolicyError(...), new errors.ForbiddenError(...), or new errors.UnauthorizedError(...)).

Backend customization

有关包括 GraphQL 支持和 policyContext API 在内的完整政策参考,请参阅 Policies

🌐 For the full policy reference including GraphQL support and the policyContext API, see Policies.

中间件

🌐 Middlewares

中间件是一个 Koa 风格的函数,用于封装请求/响应周期。与 策略(它们是通过/不通过的守卫)不同,中间件可以在请求到达控制器之前读取和修改请求,并在控制器执行后修改响应。

🌐 A middleware is a Koa-style function that wraps the request/response cycle. Unlike policies (which are pass/fail guards), middlewares can read and modify the request before it reaches the controller, and modify the response after the controller has executed.

插件可以通过两种方式导出中间件:

🌐 Plugins can export middlewares in 2 ways:

  • 作为路由级中间件,在服务器入口文件的 middlewares 导出中声明,并在路由 config.middlewares 中引用
  • 作为服务器级中间件,通过 strapi.server.use()register() 中直接注册到 Strapi HTTP 服务器上

路由级中间件

🌐 Route-level middlewares

路由级中间件作用于特定路由,并且声明方式类似于策略:作为命名工厂函数的对象,然后在路由配置中引用。

🌐 Route-level middlewares are scoped to a specific route and are declared like policies: as an object of named factory functions, then referenced in the route config.

注意这个两级签名:外层函数接收 (config, { strapi }) 并返回实际的 Koa 中间件 async (ctx, next) => {}。这允许 Strapi 将每条路由的配置传递给该函数。

🌐 Note the two-level signature: the outer function receives (config, { strapi }) and returns the actual Koa middleware async (ctx, next) => {}. This allows Strapi to pass per-route configuration to the function.

Note
  • middlewares 从插件中导出中间件函数,以便在路由配置中引用和重用。
  • strapi.server.use(...) 将一个中间件附加到全局服务器管道上。
  • 中间件的执行是基于请求的:一旦附加到路由或服务器管道,它会在每个匹配的请求中运行。
/src/plugins/my-plugin/server/src/middlewares/index.js
'use strict';

const logRequest = require('./log-request');

module.exports = {
logRequest,
};
/src/plugins/my-plugin/server/src/middlewares/log-request.js
'use strict';

module.exports = (config, { strapi }) => async (ctx, next) => {
strapi.log.info(`[my-plugin] ${ctx.method} ${ctx.url}`);
await next();
strapi.log.info(`[my-plugin] → ${ctx.status}`);
};

在路由中使用与策略相同的 plugin::my-plugin.middleware-name 命名空间引用路由级中间件:

🌐 Reference a route-level middleware in a route using the same plugin::my-plugin.middleware-name namespace as policies:

/src/plugins/my-plugin/server/src/routes/index.js
'use strict';

module.exports = [
{
method: 'POST',
path: '/articles',
handler: 'article.create',
config: {
middlewares: ['plugin::my-plugin.logRequest'],
},
},
{
method: 'GET',
path: '/articles',
handler: 'article.find',
config: {
middlewares: [
async (ctx, next) => {
// inline middleware, no registration needed
ctx.query.pageSize = ctx.query.pageSize || '10';
await next();
},
],
},
},
];

服务器级中间件

🌐 Server-level middlewares

服务器级中间件直接注册在 Strapi HTTP 服务器上,并且针对每个请求运行,而不仅仅是插件路由。在 register() 中使用 strapi.server.use() 注册它:

🌐 A server-level middleware is registered on the Strapi HTTP server directly and runs for every request, not just plugin routes. Register it in register() using strapi.server.use():

/src/plugins/my-plugin/server/src/register.js
'use strict';

module.exports = ({ strapi }) => {
// Attached to the global server pipeline — runs per matching request
strapi.server.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
ctx.set('X-Response-Time', `${ms}ms`);
});
};
Caution

服务器级中间件会影响所有插件和应用本身的所有路由,而不仅仅是你插件的路由。一个抛出异常或从未调用 next() 的服务器级中间件会破坏服务器上的每一个请求,而不仅仅是你插件的端点。当关心的问题仅涉及你插件的端点时,请使用路由级中间件。

🌐 Server-level middlewares affect all routes across all plugins and the application itself, not just your plugin's routes. A server-level middleware that throws or never calls next() will break every request on the server, not just your plugin's endpoints. Use route-level middlewares when the concern is specific to your plugin's endpoints.

Version/runtime behavior

对于路由声明,验证接受形状为 { name, options } 的对象条目,用于 policiesmiddlewares(见 services/server/routing.ts)。

🌐 For route declarations, validation accepts object entries shaped as { name, options } for both policies and middlewares (see services/server/routing.ts).

在运行时,一些内部仍然在中间件解析器(services/server/middleware.ts)中引用 { resolve, config } 支持,但标准路由文件中的路由验证不接受该形状。

🌐 At runtime, some internals still reference { resolve, config } support in the middleware resolver (services/server/middleware.ts), but that shape is not accepted by route validation in standard route files.

为了避免验证错误,请在路由配置中使用 { name, options }

🌐 To avoid validation errors, use { name, options } in route configurations.

Backend customization

有关完整的中间件参考,请参见 中间件

🌐 For the full middleware reference, see Middlewares.

最佳实践

🌐 Best practices

  • 在策略中使用 policyContext,而不是 ctx 策略的第一个参数是 policyContext,它是 Koa 上下文的一个封装。正确使用它可以确保策略在 REST 和 GraphQL 解析器中都能正常工作。
  • 明确地从策略中返回。 返回 undefined 的策略被视为允许(允许)的策略。要允许请始终返回 true,要拒绝请始终返回 false。如果意图是阻止请求,请勿隐式返回。
  • 优先使用路由级中间件而非服务器级中间件。 服务器级中间件会在整个 Strapi 服务器的每个请求上运行。除非该行为确实适用于所有流量,否则将中间件范围限制在插件路由上。
  • 在中间件中始终调用 await next() 忘记 next() 意味着请求链被中断,控制器永远不会执行,导致请求挂起且没有响应。
  • 使用 options 来实现可重用的策略。 当相同的策略逻辑在不同路由中需要不同的参数(例如,必需的角色名称)时,可以从路由的 { name, options } 对象中传递这些参数。这些值会在策略函数的 config 参数中接收。这可以避免重复类似的策略。