如何在没有插件的情况下使用自定义表和FETCH()强制对密码保护页面的REST API进行身份验证

时间:2021-07-06 作者:Jürgen Fink

我正在实施REST API 具有fetch() 作为请求的承诺Password protected 带有的页面custom table 不使用插件(仅使用WP REST API 这已经被合并到WordPress核心-向WordPress团队竖起大拇指,这是一个明智的举动,谢谢)。

一切都很好,只是我不能Authorization 在HTTP请求的标头中工作(始终传递,即使没有JWT或false标记,请参阅最后一步6.) 最后)。

以下是我的步骤:

1.)在MySQL中创建自定义表

MySQL database on my WordPress installation with custom table your_custom_table, 4 columns (id, column_1, column_2, timestamp) and 6 initials records

使用自定义表安装WordPress上的MySQL数据库your_custom_table, 4列(id, column_1, column_2, timestamp) 和6个首字母记录。

2.)创建受密码保护的页面

The resulting page with Password login二六 (HTML 带内联<script> ... </script>):

<form id="form1" name="form1">
  <button id="get" onclick="getValues()">GET</button>
  <button id="insert" onclick="insertValues()">CREATE</button>
  <button id="update" onclick="updateValues()">UPDATE</button>
</form>

<script>
let yourData = [];
let yourDataNew = {};
let yourDataUpdated = {};
let token = "";

function getValues() {
  event.preventDefault();
  //READ data
  getYourData();
};
function insertValues() {
  event.preventDefault();
  //CREATE new datarecord
  yourDataNew = {"column_1": "test-1", "column_2": "test-2"};
  insertYourData(yourDataNew);
};
function updateValues() {
  event.preventDefault();
  //UPDATE datarecord
  let idOfLastRecord = yourData[yourData.length-1].id;
  yourDataUpdated = {"id": idOfLastRecord, "column_1": "test-1-modified", "column_2": "test-2-modified"};
  updateYourData(yourDataUpdated);
};

//GET value of Access Cookie wp-postpass_{hash}
token = ("; "+document.cookie).split("; wp-postpass_675xxxxxx   =").pop().split(";").shift();
//token = \'24P%24BhPU2oE3ux8v4FFfSFbB9onTPNnglM.\'
console.log(\'TOKEN: \' + token);

// Here comes the REST API part:
// HTTP requests with fetch() promises
function getYourData() {
  let url = \'https://oilamerica.com.pa/wp-json/wp/v2/your_private_page/data\';
  fetch(url, {
    method: \'GET\',
    credentials: \'same-origin\',
    headers:{
        \'Content-Type\': \'application/json\',
        //credentials: \'same-origin\',   <-- no authorization needed
        \'Accept\': \'application/json\',   
        //\'Authorization\': \'Bearer \' + token  <-- no authorization needed
    }
  }).then(res => res.json())
  .then(response => get_success(response))
  .catch(error => failure(error));
};

function insertYourData(data) {
  let url = \'https://oilamerica.com.pa/wp-json/wp/v2/your_private_page/data\';
  fetch(url, {
    method: \'POST\',
    credentials: \'same-origin\',
    headers:{
        \'Content-Type\': \'application/json\',
        \'Accept\': \'application/json\',
        \'Authorization\': \'Bearer \' + token
    },
    body: JSON.stringify(data)
  }).then(res => res.json())
  .then(response => create_success(response))
  .catch(error => failure(error));
};

function updateYourData(data) {
  let url = \'https://oilamerica.com.pa/wp-json/wp/v2/your_private_page/data\';
  fetch(url, {
    method: \'PUT\',
    credentials: \'same-origin\',
    headers:{
        \'Content-Type\': \'application/json\',
        \'Accept\': \'application/json\',
        \'Authorization\': \'Bearer \' + token
    },
    body: JSON.stringify(data)
  }).then(res => res.json())
  .then(response => update_success(response))
  .catch(error => failure(error));
};

// fetch() promises success functions:
function get_success(json) {
  data = JSON.stringify(json);
  yourData = JSON.parse(data);
  console.log(\'GET\');
  console.log(yourData);
};
function create_success(json) {
  let insertResponse = JSON.stringify(json);
  insertResponse = JSON.parse(insertResponse);
  console.log(\'CREATE\');
  console.log(insertResponse);
};
function update_success(json) {
  let updateResponse = JSON.stringify(json);
  updateResponse = JSON.parse(updateResponse);
  console.log(\'UPDATE\');
  console.log(updateResponse);
};
function failure(error) {
  console.log("Error: " + error);
};

</script>
添加后Password Protected 第“”页您的私人页面;我得到以下结果:

The resulting page with Password login

使用密码登录的结果页面。

请注意,页面显示在页面的小部件中,即使您的菜单中没有包含页面,而在;“私人”;对于未经授权的访问者,该页面不可见。

3.)已编辑的函数。我安装的主题的php

Added my php-code in function.php of my installed theme

Code (添加了我的PHP 代码输入function.php 我安装的主题的)

/**
 * Add here your custom CRUD functions
 */
function get_your_data($request) {
    global $wpdb;
    $yourdata = $wpdb->get_results("SELECT * FROM your_custom_table");
    return rest_ensure_response( $yourdata );
};

function insert_your_data($request) {
    global $wpdb;
    $contentType = isset($_SERVER["CONTENT_TYPE"]) ? trim($_SERVER["CONTENT_TYPE"]) : \'\';

    if ($contentType === "application/json") {
        $content = trim(file_get_contents("php://input"));
        $decoded = json_decode($content, true);
        $newrecord = $wpdb->insert( \'your_custom_table\', array( \'column_1\' => $decoded[\'column_1\'], \'column_2\' => $decoded[\'column_2\']));
    };
    if($newrecord){
        return rest_ensure_response($newrecord);
    }else{
        //something gone wrong
        return rest_ensure_response(\'failed\');
    };
    header("Content-Type: application/json; charset=UTF-8");
};
function update_your_data() {
    global $wpdb;
    $contentType = isset($_SERVER["CONTENT_TYPE"]) ? trim($_SERVER["CONTENT_TYPE"]) : \'\';

    if ($contentType === "application/json") {
        $content = trim(file_get_contents("php://input"));
        $decoded = json_decode($content, true);
        $updatedrecord = $wpdb->update( \'your_custom_table\', array( \'column_1\' => $decoded[\'column_1\'], \'column_2\' => $decoded[\'column_2\']), array(\'id\' => $decoded[\'id\']), array( \'%s\' ));
    };
    if($updatedrecord){
        return rest_ensure_response($updatedrecord);
    }else{
        //something gone wrong
        return rest_ensure_response(\'failed\');
    };
    header("Content-Type: application/json; charset=UTF-8");
};

add_action(\'rest_api_init\', function() {
    /**
    * Register here your custom routes for your CRUD functions here
    */
    register_rest_route( \'wp/v2/your_private_page\', \'/data\', array(
        array(
           \'methods\'  => \'GET\',
           \'callback\' => \'get_your_data\'
        ),
        array(
           \'methods\'  => \'POST\',
           \'callback\' => \'insert_your_data\'
        ),
        array(
           \'methods\'  => \'PUT\',
           \'callback\' => \'update_your_data\'
        ),
    ));
});

4。)测试Cookie值检索部分(JWT令牌)

Retrieving the cookie value (which is the JWT token)

正在检索cookiewp_postpass_ 675xxxxx值(即JWT token) 而且做得很好

5.)测试CRUD函数(GET、POST和PUT)

GET: 工作正常(未经授权,因为在我们的前端读取数据是公开的)GET worked as expected

POST: 工作良好(经授权,即受保护)POST worked as expected

新记录为inserted 正确设置到我的自定义表:

New record inserted correctly

PUT: 工作良好(经授权,即受保护)PUT worked as expected

上次记录已updated 在我的自定义表中正确

enter image description here

6。)在没有令牌的情况下测试我的CRUD函数unauthorized 访问方式:

  1. Eliminating 手动令牌过帐false token 在HTTP标头中\'Authorization\': \'Bearer \' + false-tokenexecute 我的内联<script> ... </script> 从另一个具有no password protectionBIG SURPRISE :

在所有3种情况下,我的“POST”和“PUT”请求PASSED !! (我以为会失败)。

也就是说,\'Authorization\': \'Bearer \' + token did not force 自动地Authorization 为了prevent unauthorized access 到我的REST API。

因此,现在我的问题来了:

我怎样才能force Authorization 我对REST API的请求,以便unauthorized visitors cannot modify 我们的数据(“POST”和“PUT”)?

欢迎提出任何意见和建议。

非常感谢。

1 个回复
最合适的回答,由SO网友:Jürgen Fink 整理而成

仔细研究WordPress之后REST API Handbook 关于

Home / REST API Handbook / Extending the REST API / Routes and Endpoints
Home / REST API Handbook / Extending the REST API / Adding Custom Endpoints

我意识到我犯了几个错误。

因此,我想与大家分享我的发现。

此外,出于好奇,最后我还有一些额外的问题。

将对象请求传递给回调函数1。)将对象“$request”传递给回调函数:之前:function get_your_data() { ...
之后:function get_your_data($request) { ...

2。)在外部创建命名空间和路由wp/v2 使用您自己的版本号:

之前:register_rest_route( \'wp/v2/your_private_page\', \'/data\', array( ...
之后:register_rest_route( \'your_private_page/v1\', \'/data\', array( ...

还要在客户端脚本中调整http请求URL:

之前:let url = \'https://example.com/wp-json/wp/v2/your_private_page/data\';
之后:let url = \'https://example.com/wp-json/your_private_page/v1/data\';

3。)将“permission\\u callback”添加到您的路由:

之前:我没有它(只是有主回调)
之后:添加prefix_get_private_data_permissions_check()Permission Callback 作用

//  Permission Callback 
// \'ypp\' is the Prefix I chose (ypp = Your Private Page)

function ypp_get_private_data_permissions_check() {
    // Restrict endpoint to browsers that have the wp-postpass_ cookie.

    if ( !isset($_COOKIE[\'wp-postpass_\'. COOKIEHASH] )) {
       return new WP_Error( \'rest_forbidden\', esc_html__( \'OMG you can not create or edit private data.\', \'my-text-domain\' ), array( \'status\' => 401 ) );
    };
    // This is a black-listing approach. You could alternatively do this via white-listing, by returning false here and changing the permissions check.
    return true;
};

// And then add the permission_callback to your POST and PUT routes:

add_action(\'rest_api_init\', function() {
    /**
    * Register here your custom routes for your CRUD functions
    */
    register_rest_route( \'your_private_page/v1\', \'/data\', array(
       array(
          \'methods\'  => WP_REST_Server::READABLE,
          \'callback\' => \'get_your_data\',
          // Always allow.
          \'permission_callback\' => \'__return_true\' // <-- good practice, according to docs
       ),
       array(
          \'methods\'  => WP_REST_Server::CREATABLE,
          \'callback\' => \'insert_your_data\',
          // Here we register our permissions callback. The callback is fired before the main callback to check if the current user can access the endpoint.
          \'permission_callback\' => \'ypp_get_private_data_permissions_check\', // <-- that was the missing part
       ),
       array(
          \'methods\'  => WP_REST_Server::EDITABLE,
          \'callback\' => \'update_your_data\',
          // Here we register our permissions callback. The callback is fired before the main callback to check if the current user can access the endpoint.
          \'permission_callback\' => \'ypp_get_private_data_permissions_check\', // <-- that was the missing part
       ),
    ));
});
显然,这很有意义,因为它在服务器端(WordPress、php)必须进行授权(虚设我,呵呵)

(完整代码)完整代码:以下代码片段适用于我的自托管WordPress安装:

WordPress: 版本5.7.2PHP: 7.4版host: hostmonster。com
client: Windows 10browsers: 在Chrome、Firefox甚至Edge上进行了测试

Code (HTML 带内联<script> ... </script>):

<form id="form1" name="form1">
  <button id="get" onclick="getValues()">GET</button>
  <button id="insert" onclick="insertValues()">CREATE</button>
  <button id="update" onclick="updateValues()">UPDATE</button>
</form>

<script>
let yourData = [];
let yourDataNew = {};
let yourDataUpdated = {};
let token = "";

function getValues() {
  event.preventDefault();
  //READ data
  getYourData();
};
function insertValues() {
  event.preventDefault();
  //CREATE new datarecord
  yourDataNew = {"column_1": "test-1", "column_2": "test-2"};
  insertYourData(yourDataNew);
};
function updateValues() {
  event.preventDefault();
  //UPDATE datarecord
  let idOfLastRecord = yourData[yourData.length-1].id;
  yourDataUpdated = {"id": idOfLastRecord, "column_1": "test-1-modified", "column_2": "test-2-modified"};
  updateYourData(yourDataUpdated);
};

// We don\'t need to retrieve token form Cookie. See my EDIT at the end

//GET value of Access Cookie wp-postpass_{hash}
//token = ("; "+document.cookie).split("; wp-postpass_675xxxxxx   =").pop().split(";").shift(); 
//console.log(\'TOKEN: \' + token);

// Here comes the REST API part:
// HTTP requests with fetch() promises
function getYourData() {
  let url = \'https://example.com/wp-json/your_private_page/v1/data\';
  fetch(url, {
    method: \'GET\',
    credentials: \'same-origin\',
    headers:{
        \'Content-Type\': \'application/json\',
        \'Accept\': \'application/json\',
        //\'Authorization\': \'Bearer \' + token  <-- not needed, see EDIT at end
    }
  }).then(res => res.json())
  .then(response => get_success(response))
  .catch(error => failure(error));
};

function insertYourData(data) {
  let url = \'https://example.com/wp-json/your_private_page/v1/data\';
  fetch(url, {
    method: \'POST\',
    credentials: \'same-origin\',
    headers:{
        \'Content-Type\': \'application/json\',
        \'Accept\': \'application/json\',
        //\'Authorization\': \'Bearer \' + token  <-- not needed, see EDIT at end
    },
    body: JSON.stringify(data)
  }).then(res => res.json())
  .then(response => create_success(response))
  .catch(error => failure(error));
};

function updateYourData(data) {
  let url = \'https://example.com/wp-json/your_private_page/v1/data\';
  fetch(url, {
    method: \'PUT\',
    credentials: \'same-origin\',
    headers:{
        \'Content-Type\': \'application/json\',
        \'Accept\': \'application/json\',
        //\'Authorization\': \'Bearer \' + token  <-- not needed, see EDIT at end
    },
    body: JSON.stringify(data)
  }).then(res => res.json())
  .then(response => update_success(response))
  .catch(error => failure(error));
};

// fetch() promises success functions:
function get_success(json) {
  data = JSON.stringify(json);
  yourData = JSON.parse(data);
  console.log(\'GET\');
  console.log(yourData);
};
function create_success(json) {
  let insertResponse = JSON.stringify(json);
  insertResponse = JSON.parse(insertResponse);
  console.log(\'CREATE\');
  console.log(insertResponse);
};
function update_success(json) {
  let updateResponse = JSON.stringify(json);
  updateResponse = JSON.parse(updateResponse);
  console.log(\'UPDATE\');
  console.log(updateResponse);
};
function failure(error) {
  console.log("Error: " + error);
};

</script>
Code (PHP 代码输入function.php 我的已安装主题):

/**
 * Add here your custom CRUD functions
 */

/**
  * This is our callback function to return (GET) our data.
  *
  * @param WP_REST_Request $request This function accepts a rest request to process data.
  */
function get_your_data($request) {
    global $wpdb;
    $yourdata = $wpdb->get_results("SELECT * FROM your_custom_table");

    return rest_ensure_response( $yourdata );
};

/**
 * This is our callback function to insert (POST) new data record.
 *
 * @param WP_REST_Request $request This function accepts a rest request to process data.
 */
function insert_your_data($request) {
    global $wpdb;
    $contentType = isset($_SERVER["CONTENT_TYPE"]) ? trim($_SERVER["CONTENT_TYPE"]) : \'\';

    if ($contentType === "application/json") {
        $content = trim(file_get_contents("php://input"));
        $decoded = json_decode($content, true);
        $newrecord = $wpdb->insert( \'your_custom_table\', array( \'column_1\' => $decoded[\'column_1\'], \'column_2\' => $decoded[\'column_2\']));
    };
    if($newrecord){
        return rest_ensure_response($newrecord);
    }else{
        //something gone wrong
        return rest_ensure_response(\'failed\');
    };

    header("Content-Type: application/json; charset=UTF-8");
};
/**
 * This is our callback function to update (PUT) a data record.
 *
 * @param WP_REST_Request $request This function accepts a rest request to process data.
 */
function update_your_data($request) {
    global $wpdb;
    $contentType = isset($_SERVER["CONTENT_TYPE"]) ? trim($_SERVER["CONTENT_TYPE"]) : \'\';

    if ($contentType === "application/json") {
        $content = trim(file_get_contents("php://input"));
        $decoded = json_decode($content, true);
        $updatedrecord = $wpdb->update( \'your_custom_table\', array( \'column_1\' => $decoded[\'column_1\'], \'column_2\' => $decoded[\'column_2\']), array(\'id\' => $decoded[\'id\']), array( \'%s\' ));
    };

    if($updatedrecord){
        return rest_ensure_response($updatedrecord);
    }else{
        //something gone wrong
        return rest_ensure_response(\'failed\');
    };

    header("Content-Type: application/json; charset=UTF-8");
};

// \'ypp\' is the Prefix I chose (ypp = Your Private Page)
function ypp_get_private_data_permissions_check() {
    // Restrict endpoint to browsers that have the wp-postpass_ cookie.

    if ( !isset($_COOKIE[\'wp-postpass_\'. COOKIEHASH] )) {
        return new WP_Error( \'rest_forbidden\', esc_html__( \'OMG you can not create or edit private data.\', \'my-text-domain\' ), array( \'status\' => 401 ) );
    };
    // This is a black-listing approach. You could alternatively do this via white-listing, by returning false here and changing the permissions check.
    return true;
};

add_action(\'rest_api_init\', function() {
    /**
    * Register here your custom routes for your CRUD functions
    */
    register_rest_route( \'your_private_page/v1\', \'/data\', array(
        array(
           \'methods\'  => WP_REST_Server::READABLE,
           \'callback\' => \'get_your_data\',
           // Always allow.
           \'permission_callback\' => \'__return_true\'
        ),
        array(
           \'methods\'  => WP_REST_Server::CREATABLE,
           \'callback\' => \'insert_your_data\',
           // Here we register our permissions callback. The callback is fired before the main callback to check if the current user can access the endpoint.
           \'permission_callback\' => \'ypp_get_private_data_permissions_check\',
        ),
        array(
           \'methods\'  => WP_REST_Server::EDITABLE,
           \'callback\' => \'update_your_data\',
           // Here we register our permissions callback. The callback is fired before the main callback to check if the current user can access the endpoint.
           \'permission_callback\' => \'ypp_get_private_data_permissions_check\',
        ),
    ));
});

5.) Testing

Finally got my so desperately anticipated FAILURE

终于得到了我如此绝望的期待FAILURE

最后一个问题,尽管:

Is \'Authorization\': \'Bearer \' + token necessary in header of HTTP request?

经过一些测试,我意识到if ( !isset($_COOKIE[\'wp-postpass_\'. COOKIEHASH] )) { 在权限回调中,不仅检查是否在客户端浏览器上设置了Cookie,而且似乎还检查其值(JWT标记)。

因为我用我的初始代码进行了dobble检查,传递了一个错误的令牌,消除了cookie,或者让会话保持打开状态,但在后端更改了站点的密码(因此WordPress将创建一个新的令牌,因此set的值wp_postpass_ cookie将更改)并且所有测试都正确进行-REST API被阻止,not only verifying presence of cookie, but also its value (这很好-谢谢WordPress团队)。

My Questions here: