各位听众,大家好!今天咱们来聊聊WordPress的REST API,特别是wp-includes/rest-api.php
这个核心文件中的路由注册和权限验证。这玩意儿就像WordPress的大门,你得知道怎么开门进屋,才能跟它好好玩耍。
开场白:REST API是啥?跟WordPress有啥关系?
简单来说,REST API就是一套规则,让不同的程序(比如你的手机APP、前端框架、或者其他网站)能够通过网络来访问和操作WordPress里的数据。想象一下,你不用登录WordPress后台,就能用代码发篇文章、改个标题,是不是很酷?
WordPress REST API让WordPress不仅仅是个博客系统,而是一个可以被各种应用利用的数据平台。
主角登场:wp-includes/rest-api.php
这个文件是WordPress REST API的“启动器”。它负责初始化REST API,注册默认的路由,以及加载其他的REST API控制器。你可以把它想象成一个总指挥,负责安排各个“演员”(控制器)出场。
第一幕:路由注册(Routing)—— 指挥交通的关键
路由,说白了,就是URL和处理函数之间的对应关系。当用户访问某个特定的URL时,WordPress就知道该调用哪个函数来处理请求。
在wp-includes/rest-api.php
中,rest_api_init
钩子是路由注册的核心。WordPress会在init
钩子之后,调用rest_api_init
,让你有机会注册自己的路由。
add_action( 'rest_api_init', 'my_register_custom_routes' );
function my_register_custom_routes() {
// 注册一个路由,用于获取特定作者的文章
register_rest_route(
'my-plugin/v1', // 命名空间,类似于模块的名字
'/authors/(?P<id>d+)/posts', // 路由,支持正则表达式
array(
'methods' => 'GET', // 请求方法,可以是GET, POST, PUT, DELETE等
'callback' => 'my_get_author_posts', // 回调函数,用于处理请求
'args' => array(
'id' => array(
'validate_callback' => 'is_numeric', // 参数验证,确保id是数字
'sanitize_callback' => 'absint', // 参数清理,转换为绝对整数
),
),
'permission_callback' => 'my_check_permission' //权限校验
)
);
}
function my_get_author_posts( $request ) {
$author_id = $request['id'];
$args = array(
'author' => $author_id,
);
$posts = get_posts( $args );
if ( empty( $posts ) ) {
return new WP_Error( 'no_posts', 'No posts found for this author.', array( 'status' => 404 ) );
}
$data = array();
foreach ($posts as $post) {
$data[] = array(
'id' => $post->ID,
'title' => $post->post_title,
'content' => $post->post_content,
);
}
return rest_ensure_response( $data );
}
function my_check_permission( $request ) {
// 只有登录用户才能访问
if ( ! is_user_logged_in() ) {
return new WP_Error( 'rest_forbidden', 'You must be logged in to access this resource.', array( 'status' => 401 ) );
}
return true;
}
代码解读:
-
register_rest_route()
: 这个函数是注册路由的关键。'my-plugin/v1'
: 命名空间,避免不同插件的路由冲突。建议使用插件名/版本号的格式。'/authors/(?P<id>d+)/posts'
: 路由的URL。(?P<id>d+)
是一个正则表达式,用于匹配作者ID,并将其作为参数传递给回调函数。array(...)
: 包含路由的详细信息,如请求方法、回调函数、参数验证等。'methods' => 'GET'
: 指定允许的HTTP方法。'callback' => 'my_get_author_posts'
: 指定处理请求的回调函数。'args'
: 定义路由参数,并进行验证和清理。'validate_callback'
: 验证参数是否符合预期。'sanitize_callback'
: 清理参数,防止安全问题。
'permission_callback' => 'my_check_permission'
: 权限校验回调函数。
-
my_get_author_posts( $request )
: 回调函数,负责处理请求,并返回数据。$request
: 包含了请求的所有信息,包括参数、请求头等。rest_ensure_response()
: 确保返回的数据是一个WP_REST_Response
对象,这是REST API的标准响应格式。WP_Error
: 用于返回错误信息。
-
my_check_permission( $request )
: 权限校验回调函数。
命名空间(Namespace):规划你的地盘
命名空间就像是你在WordPress REST API里的地盘。使用正确的命名空间,可以避免不同插件之间的路由冲突。通常,命名空间的格式是插件名/版本号
,例如my-plugin/v1
。
正则表达式:路由的精确匹配
正则表达式让你可以定义更复杂的路由规则。比如,你可以用正则表达式匹配不同格式的日期、ID等。
HTTP方法:GET, POST, PUT, DELETE
REST API支持多种HTTP方法,每种方法都有不同的含义:
HTTP方法 | 含义 | 例子 |
---|---|---|
GET | 获取资源。通常用于读取数据,不应该修改服务器上的数据。 | 获取文章列表、获取特定文章的内容。 |
POST | 创建资源。通常用于提交表单、创建新的文章等。 | 创建一篇新的文章。 |
PUT | 更新资源。通常用于完全替换一个已存在的资源。 | 完整替换一篇已存在的文章的内容。 |
DELETE | 删除资源。通常用于删除文章、评论等。 | 删除一篇文章。 |
PATCH | 部分更新资源。通常用于只修改资源的部分字段,而不是完全替换。与PUT的区别是PATCH只需要传递需要修改的字段,而PUT需要传递所有字段。虽然REST规范建议使用PATCH,但WordPress REST API核心并没有完全实现PATCH方法,通常还是使用PUT。 | 只修改文章的标题,而不修改内容。 |
参数验证和清理:安全第一
在处理用户传递的参数之前,务必进行验证和清理。这可以防止SQL注入、XSS攻击等安全问题。
validate_callback
: 验证参数是否符合预期。例如,你可以用is_numeric()
函数验证参数是否是数字。sanitize_callback
: 清理参数,去除不安全的字符。例如,你可以用absint()
函数将参数转换为绝对整数。sanitize_text_field
函数可以用来清理文本,移除HTML标签和编码特殊字符。
第二幕:权限验证(Authentication & Authorization)—— 守门神的职责
权限验证是REST API安全的关键。你需要确保只有经过授权的用户才能访问和操作数据。
WordPress REST API提供了多种权限验证方式:
- Cookies: 如果你是从WordPress网站的前端发送请求,通常可以使用Cookies进行权限验证。用户登录后,WordPress会在Cookie中存储用户的登录信息。
- Nonce: Nonce是一种一次性使用的令牌,可以防止CSRF攻击。
- OAuth: OAuth是一种更安全的权限验证方式,允许第三方应用在用户授权的情况下访问WordPress API。
- JWT (JSON Web Tokens): JWT 是一种基于标准的、自包含的、安全的传输JSON对象的开放标准。可以用于授权和信息交换。
permission_callback
:权限验证的核心
在注册路由时,你可以指定一个permission_callback
函数,用于进行权限验证。这个函数接收一个WP_REST_Request
对象作为参数,并返回true
表示允许访问,返回WP_Error
对象或false
表示拒绝访问。
function my_check_permission( $request ) {
// 只有管理员才能访问
if ( ! current_user_can( 'manage_options' ) ) {
return new WP_Error( 'rest_forbidden', 'You do not have permission to access this resource.', array( 'status' => 401 ) );
}
return true;
}
代码解读:
current_user_can( 'manage_options' )
: 这个函数用于检查当前用户是否具有manage_options
权限,这是管理员的权限。WP_Error
: 用于返回错误信息。status
字段指定了HTTP状态码,401
表示未授权。
常用的权限验证函数:
函数 | 含义 |
---|---|
is_user_logged_in() |
检查用户是否已登录。 |
current_user_can( $capability ) |
检查当前用户是否具有指定的权限。 |
wp_verify_nonce( $nonce, $action ) |
验证Nonce是否有效,用于防止CSRF攻击。 |
apply_filters( 'rest_authentication_errors', $result ) |
允许插件修改权限验证结果。 |
第三幕:REST API Controller—— 控制器的职责
虽然我们可以直接在rest_api_init
钩子中注册路由和编写回调函数,但更好的做法是使用REST API Controller。Controller可以将路由、权限验证、数据处理等逻辑封装在一起,使代码更易于维护和扩展。
WordPress 核心已经提供了很多Controller,比如:WP_REST_Posts_Controller
、WP_REST_Users_Controller
、WP_REST_Terms_Controller
。
自定义Controller的步骤:
- 创建一个类,继承
WP_REST_Controller
。 - 定义
register_routes()
方法,用于注册路由。 - 定义处理请求的方法,如
get_item()
、create_item()
、update_item()
、delete_item()
。 - 注册Controller。
<?php
/**
* My Custom Post Type Controller
*/
class My_REST_CPT_Controller extends WP_REST_Controller {
/**
* 命名空间和版本
*
* @var string
*/
protected $namespace = 'my-plugin/v1';
/**
* 路由名称
*
* @var string
*/
protected $rest_base = 'my-cpts';
/**
* 注册路由
*
* @return void
*/
public function register_routes() {
register_rest_route( $this->namespace, '/' . $this->rest_base, array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => array(),
),
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'create_item' ),
'permission_callback' => array( $this, 'create_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
),
) );
register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[d]+)', array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(
'context' => array(
'default' => 'view',
),
),
),
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'update_item' ),
'permission_callback' => array( $this, 'update_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
),
array(
'methods' => WP_REST_Server::DELETABLE,
'callback' => array( $this, 'delete_item' ),
'permission_callback' => array( $this, 'delete_item_permissions_check' ),
'args' => array(
'force' => array(
'default' => false,
),
),
),
) );
register_rest_route( $this->namespace, '/' . $this->rest_base . '/schema', array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_public_item_schema' ),
),
) );
}
/**
* 获取项目集合
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error
*/
public function get_items( $request ) {
$data = array();
// 这里替换成你自己的数据获取逻辑
for ( $i = 1; $i < 2; $i++ ) {
$itemdata = $this->prepare_item_for_response( (object)array('id' => $i, 'title' => "Item {$i}"), $request );
$data[] = $this->prepare_response_for_collection( $itemdata );
}
// return a collection of response objects
return new WP_REST_Response( $data, 200 );
}
/**
* 获取单个项目
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error
*/
public function get_item( $request ) {
$item = array();
$params = $request->get_params();
$id = (int) $params['id'];
$item['id'] = $id;
$item['title'] = "Item {$id}";
// 这里替换成你自己的数据获取逻辑
$data = $this->prepare_item_for_response( (object)$item, $request );
// return a response or error based on some conditional
if ( 1 == 1 ) {
return new WP_REST_Response( $data, 200 );
} else {
return new WP_Error( 'code', __( 'message', 'text-domain' ) );
}
}
/**
* 创建项目
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error
*/
public function create_item( $request ) {
$item = $this->prepare_item_for_database( $request );
if ( is_wp_error( $item ) ) {
return $item;
}
// 这里替换成你自己的数据处理逻辑
$item['id'] = rand(10,100);
$item['title'] = "Item {$item['id']}";
$data = $this->prepare_item_for_response( (object)$item, $request );
return new WP_REST_Response( $data, 200 );
}
/**
* 更新项目
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error
*/
public function update_item( $request ) {
$item = array();
$params = $request->get_params();
$id = (int) $params['id'];
$item['id'] = $id;
$item['title'] = "Item {$id} - Updated";
// 这里替换成你自己的数据处理逻辑
$data = $this->prepare_item_for_response( (object)$item, $request );
return new WP_REST_Response( $data, 200 );
}
/**
* 删除项目
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error
*/
public function delete_item( $request ) {
$item = array();
$params = $request->get_params();
$id = (int) $params['id'];
$item['id'] = $id;
$item['title'] = "Item {$id} - Deleted";
// 这里替换成你自己的数据处理逻辑
$data = $this->prepare_item_for_response( (object)$item, $request );
return new WP_REST_Response( $data, 200 );
}
/**
* 检查是否有获取多个项目的权限
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has read access, WP_Error object otherwise.
*/
public function get_items_permissions_check( $request ) {
if ( ! current_user_can( 'read' ) ) {
return new WP_Error( 'rest_forbidden', __( 'You cannot view the resource.', 'my-text-domain' ), array( 'status' => $this->authorization_status_code() ) );
}
return true;
}
/**
* 检查是否有获取单个项目的权限
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has read access, WP_Error object otherwise.
*/
public function get_item_permissions_check( $request ) {
return $this->get_items_permissions_check( $request );
}
/**
* 检查是否有创建项目的权限
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has read access, WP_Error object otherwise.
*/
public function create_item_permissions_check( $request ) {
if ( ! current_user_can( 'edit_posts' ) ) {
return new WP_Error( 'rest_forbidden', __( 'You cannot create the resource.', 'my-text-domain' ), array( 'status' => $this->authorization_status_code() ) );
}
return true;
}
/**
* 检查是否有更新项目的权限
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has read access, WP_Error object otherwise.
*/
public function update_item_permissions_check( $request ) {
if ( ! current_user_can( 'edit_posts' ) ) {
return new WP_Error( 'rest_forbidden', __( 'You cannot update the resource.', 'my-text-domain' ), array( 'status' => $this->authorization_status_code() ) );
}
return true;
}
/**
* 检查是否有删除项目的权限
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has read access, WP_Error object otherwise.
*/
public function delete_item_permissions_check( $request ) {
if ( ! current_user_can( 'delete_posts' ) ) {
return new WP_Error( 'rest_forbidden', __( 'You cannot delete the resource.', 'my-text-domain' ), array( 'status' => $this->authorization_status_code() ) );
}
return true;
}
/**
* 准备数据库项目
*
* @param WP_REST_Request $request Full details about the request.
* @return array|WP_Error
*/
protected function prepare_item_for_database( $request ) {
$params = $request->get_params();
$prepared_item = array();
if ( isset( $params['title'] ) ) {
if ( is_string( $params['title'] ) ) {
$prepared_item['title'] = $params['title'];
} else {
return new WP_Error( 'rest_invalid_type', __( 'Title must be a string.', 'my-text-domain' ), array( 'status' => 400 ) );
}
}
return $prepared_item;
}
/**
* 准备项目以进行响应
*
* @param mixed $item WordPress representation of the item.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response Returns a JSON-compatible representation of the item.
*/
public function prepare_item_for_response( $item, $request ) {
$data = array(
'id' => absint( $item->id ),
'title' => $item->title,
);
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
// Wrap the data in a response object.
return rest_ensure_response( $data );
}
/**
* 获取项目模式,用于参数验证
*
* @return array
*/
public function get_item_schema() {
if ( $this->schema ) {
return $this->add_additional_fields_schema( $this->schema );
}
$schema = array(
// This tells the spec of JSON Schema we are using.
'$schema' => 'http://json-schema.org/draft-04/schema#',
// The title property defines a human-readable title for the schema.
'title' => 'my-cpt',
// The type property defines the data type.
'type' => 'object',
// In addition to the standard JSON Schema defined properties, the schema may use semantic properties from the WordPress API.
'context' => array(
'description' => __( 'Scope under which the request is made; determines fields present in response.', 'my-text-domain' ),
'type' => 'string',
'enum' => array( 'view', 'edit', 'embed' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_key',
'validate_callback' => 'rest_validate_request_arg',
),
),
// Properties available in the schema.
'properties' => array(
'id' => array(
'description' => __( 'Unique identifier for the object.', 'my-text-domain' ),
'type' => 'integer',
'context' => array( 'view', 'edit', 'embed' ),
'readonly' => true,
),
'title' => array(
'description' => __( 'The title for the object.', 'my-text-domain' ),
'type' => 'string',
'context' => array( 'view', 'edit', 'embed' ),
),
),
);
$this->schema = $schema;
return $this->add_additional_fields_schema( $this->schema );
}
/**
* 获取授权状态码
*
* @return int
*/
public function authorization_status_code() {
if ( is_user_logged_in() ) {
return 403;
}
return 401;
}
}
add_action( 'rest_api_init', function () {
$controller = new My_REST_CPT_Controller();
$controller->register_routes();
} );
代码解读:
My_REST_CPT_Controller
: 自定义的Controller类,继承自WP_REST_Controller
。$namespace
和$rest_base
: 定义了命名空间和路由的基础URL。register_routes()
: 注册路由的方法。get_items()
、get_item()
、create_item()
、update_item()
、delete_item()
: 处理不同HTTP请求的方法。prepare_item_for_response()
: 格式化返回的数据。get_item_schema()
: 返回用于参数验证的JSON Schema。- 权限校验函数:
get_items_permissions_check
、get_item_permissions_check
、create_item_permissions_check
、update_item_permissions_check
、delete_item_permissions_check
。
JSON Schema:数据校验的利器
JSON Schema是一种描述JSON数据结构的规范。你可以用JSON Schema定义数据的类型、格式、范围等,用于验证用户提交的数据是否有效。
总结:
今天我们一起深入了解了WordPress REST API中的路由注册和权限验证。掌握了这些知识,你就可以构建自己的REST API,让WordPress与各种应用无缝连接。记住,安全第一,一定要做好参数验证和权限验证。
希望今天的讲解对大家有所帮助!下次再见!