Lastna končna točka REST API v WordPressu, varno

WordPress Branje: 5 min

Šestvrstični primer iz dokumentacije WordPress da delujočo končno točko REST API, vendar ne za produkcijsko delovanje. Značilna zgodba: razvijalec registrira končno točko, ki na vsako zahtevo vrne 200 KB JSON, brez preverjanja pristnosti in omejitev. Čez teden dni jo robot prenaša z več deset tisoč zahtevami na uro, spletno mesto se upočasni, napolni pa se tudi omejitev virov gostovanja.

Ta članek je namenjen razvijalcem: na kaj paziti, ko pišeš lastno končno točko, in kako pregledati obstoječe.

Vrstici, ki ju večina primerov izpusti

Končno točko registriraš s funkcijo register_rest_route(): imenski prostor, pot, metoda HTTP, callback, ki ustvari odgovor, in permission_callback.

  • permission_callback odloči, kdo lahko kliče končno točko. Če je __return_true, je končna točka javna, kliče jo lahko kdor koli iz interneta. WordPress od različice 5.5 opozori, če manjka.
  • args opisuje, katere parametre sprejema. S tipom (type), obveznostjo (required), čiščenjem (sanitize_callback) in preverjanjem (validate_callback) WordPress napačen vhod zavrne, še preden se tvoja koda izvede. Brez tega pride do funkcije callback karkoli.

Pregled obstoječih končnih točk

Vse neprivzete končne točke spletnega mesta in njihovo preverjanje pravic izpiše WP-CLI (v vmesniku Terminal gostovanja cPanel ali prek SSH):

cd ~/public_html
wp eval 'foreach ( rest_get_server()->get_routes() as $route => $handlers ) { if ( preg_match( "#^/(wp|oembed|wp-site-health|wp-block-editor)/#", $route ) || "/" === $route ) continue; foreach ( $handlers as $h ) { $p = $h["permission_callback"] ?? null; echo $route, " [", implode( ",", array_keys( $h["methods"] ) ), "] ", is_string( $p ) ? $p : ( $p ? "closure" : "NINCS" ), "\n"; } }'

Kjer je __return_true, je končna točka javna. Poglej, kaj vrača:

curl -s "https://pelda.hu/wp-json/sajat/v1/adatok" | head -c 1000

Če naslovi /wp-json/ vrnejo napako 404, so trajne povezave nastavljene na preprost način; takrat uporabi obliko https://pelda.hu/?rest_route=/sajat/v1/adatok. Če odgovor kaže e-poštne naslove, notranje identifikatorje ali vsebino osnutkov, končna točka pušča podatke in jo je treba takoj popraviti. Osnove WP-CLI opisuje članek o uporabi WP-CLI.

Končna točka za produkcijo

add_action( 'rest_api_init', function () {
    register_rest_route( 'sajat/v1', '/termekek/(?P<id>\d+)', array(
        'methods'             => WP_REST_Server::READABLE, // GET
        'callback'            => 'sajat_termek_lekerdezese',
        'permission_callback' => 'sajat_olvashatja',
        'args'                => array(
            'id' => array(
                'required'          => true,
                'type'              => 'integer',
                'minimum'           => 1,
                'sanitize_callback' => 'absint',
            ),
        ),
    ) );
} );

function sajat_olvashatja( WP_REST_Request $request ) {
    // Prijavljen uporabnik (piškotek + nonce ali geslo aplikacije).
    if ( current_user_can( 'read' ) ) {
        return true;
    }
    // Szerver-szerver kapcsolat API-kulccsal.
    $kulcs = $request->get_header( 'X-API-Key' );
    if ( $kulcs && defined( 'SAJAT_API_KULCS' ) && hash_equals( SAJAT_API_KULCS, $kulcs ) ) {
        return true;
    }
    return new WP_Error( 'rest_forbidden', 'Potrebno je preverjanje pristnosti.', array( 'status' => 401 ) );
}

function sajat_termek_lekerdezese( WP_REST_Request $request ) {
    $termek = get_post( $request['id'] );
    if ( ! $termek || 'publish' !== $termek->post_status ) {
        return new WP_Error( 'nincs_ilyen', 'Tak izdelek ne obstaja.', array( 'status' => 404 ) );
    }
    return rest_ensure_response( array(
        'id'    => $termek->ID,
        'cim'   => get_the_title( $termek ),
    ) );
}

Nekaj pravil za primerom:

  • Vrni le, kar je potrebno. Ne celotnega objekta WP_Post, temveč potrebna polja, in le objavljeno vsebino.
  • Ključ API naj ne pride v podatkovno bazo, temveč v datoteko wp-config.php (define( 'SAJAT_API_KULCS', '…' );), primerjaj pa ga s funkcijo hash_equals(), ne z ==.
  • Pri uredniških končnih točkah (na primer podatki lastnih blokov urejevalnika blokov) naj bo pravica vezana na ustrezno zmožnost: current_user_can( 'edit_post', $id ), nikoli __return_true.
  • sanitize_callback in validate_callback: prva vhod očisti (absint, sanitize_text_field), druga ga zavrne, če je neveljaven. Za preprosto preverjanje tipa zadošča tudi shema args (type, minimum, enum, format).

Preverjanje

  • Lasten JavaScript spletnega mesta (prijavljen uporabnik) se preveri z nonce wp_rest: vrednost wp_create_nonce( 'wp_rest' ) pošlji v glavi X-WP-Nonce.
  • Zunanja aplikacija, mobilna aplikacija, drug strežnik se preveri z geslom aplikacije: ustvari ga v profilu uporabnika (Uporabniki › Profil, razdelek Gesla aplikacij) in ga pošlji s preverjanjem Basic. Geslo aplikacije je mogoče preklicati posebej in ni enako prijavnemu geslu.

Javne končne točke: predpomnilnik in omejitev

Če je končna točka namerno javna, jo zaščiti pred preobremenitvijo.

Predpomnjenje. Odgovorov REST WordPress ne predpomni. Če je vsebina veljavna več minut, to sporoči CDN in brskalniku:

$valasz = rest_ensure_response( $adatok );
$valasz->header( 'Cache-Control', 'public, max-age=300' );
return $valasz;

Rezultat drage poizvedbe lahko na strežniku shraniš tudi kot prehodne podatke (set_transient()), tako ga pogoste zahteve ne izvedejo znova.

Omejitev zahtev. WordPress ne omejuje, kdo lahko končno točko kliče kolikokrat. Najučinkovitejše je pravilo CDN ali požarnega zidu (na primer pravilo omejitve zahtev Cloudflare za pot končne točke), ker pretirane zahteve tako sploh ne dosežejo gostovanja. V PHP preprost števec po naslovih IP na začetku permission_callback:

function sajat_kereskorlat() {
    $kulcs = 'kk_' . md5( $_SERVER['REMOTE_ADDR'] ?? '' );
    $db    = (int) get_transient( $kulcs );
    if ( $db >= 60 ) {
        return new WP_Error( 'rest_tul_sok_keres', 'Preveč zahtev.', array( 'status' => 429 ) );
    }
    set_transient( $kulcs, $db + 1, MINUTE_IN_SECONDS );
    return true;
}

To dovoli 60 zahtev na minuto na naslov IP. Če je spletno mesto za CDN, je REMOTE_ADDR lahko naslov CDN; takrat naslov obiskovalca vzemi iz glave, ki jo pošlje CDN (na primer iz glave Cloudflare CF-Connecting-IP), a le, če zahteva res prihaja od CDN.

Dokumentacija

register_rest_route() sprejme tudi parameter schema, ki odgovor opiše v shemi JSON. WordPress jo vrne na zahtevo OPTIONS končne točke, obdelati pa jo znajo tudi izvozniki OpenAPI in Postman.

Preizkus

curl -s -o /dev/null -w "%{http_code}\n" "https://pelda.hu/wp-json/sajat/v1/termekek/123"
curl -s -H "X-API-Key: tvoj-lasten-kljuc" "https://pelda.hu/wp-json/sajat/v1/termekek/123"
curl -s "https://pelda.hu/wp-json/sajat/v1/termekek/abc"

Brez ključa je pravilen odgovor 401, s ključem podatki izdelka, z neveljavnim identifikatorjem pa 404 ali 400. Pred uvedbo ga preizkusi na testni kopiji (staging), in če za iskanje napak potrebuješ beleženje, pomaga članek o načinu za odpravljanje napak WordPress. Splošne varnostne nastavitve WordPressa povzema ločen članek.

Nazaj: WordPress – članki (45)