LoginSignup
2
1

More than 3 years have passed since last update.

WordPressのパスワード保護記事のログイン認証にreCAPTCHA v3を追加する

Last updated at Posted at 2020-11-09

はじめに

WordPressのプラグインには、管理画面へのログインや、パスワードリセット、記事のコメント投稿など、複数のフォームデータ送信シーンに対してGoogle reCAPTCHAを追加できるものがあります。

しかし、パスワードで保護された記事のログインフォームに対応しているプラグインは、有償のオプション提供のもの以外、私は見つけられませんでした。

このため、WordPressのパスワード保護記事のログイン認証に、reCAPTCHA v3を追加した時の手順をメモします。

前提

<システム環境>

  • PHP 7.2
  • WordPressバージョン 5.5.1
  • WordPressテーマ Twenty Seventeen 2.4 (子テーマを作成してカスタマイズ)
  • WordPressプラグイン SiteGuard 1.5.2  (reCAPTCHAの動作に必要ではないのですが、インストールされている環境では考慮点があるため、記載しています)

<その他>

  • Google reCAPTCHAキー取得済み。
  • 機能は本来プラグイン化する方針だと思うのですが、今回はプラグイン化せず実装しています。

追加手順

Googleの公式マニュアル中、「最も簡単な方法」で追加します。

チャレンジをボタンに自動的にバインド

子テーマのfunctions.phpに下記コードを追加します。

functions.php
/* GoogleRecaptcha V3 */
function recaptcha_v3() {
    wp_enqueue_script( 'recaptcha_v3', 'https://www.google.com/recaptcha/api.js' );
    wp_add_inline_script( 'recaptcha_v3', "function onSubmit(token) {
        document.getElementById('post-password-form').submit();
    }", 'after' );
}
add_action( 'wp_enqueue_scripts', 'recaptcha_v3' );

追加により、reCAPTCHAの動作に必要なJavaScriptがブラウザに読み込まれます。
※パスワード保護ページ以外でも読み込まれます。

getElementByIdの引数('post-password-form')は、パスワード保護ページのログインフォームのIDを指定しています。

ログインフォームのIDは、WordPress標準では明示されていません。このため、後述のコードもfunctions.phpへ追加します。

コード中のget_the_password_form_recaptcha関数は、WordPress標準のwp-includes/post-template.phpのget_the_password_form関数をコピーして変更を加えたもので、ログインフォームの生成を処理しています。

主な変更点は、下記の通りです。

  • ログインフォームのIDを宣言する。
  • ログインフォームのアクションの送信先をwp-login.phpからwp-loginpp.phpへ変更する。
  • ログインフォームのログインボタンにreCAPTCHAの属性(サイトキー含む)を追加する。
  • ログインフォームに隠し要素(input hidden)を追加し、g-recaptcha-actionという名前を付与し、アクション名を保持させる。

この変更後の関数をadd_filterの('the_password_form',...)に指定する事で、標準のログインフォームを上書きしています。

functions.php
/**
 * Retrieve protected post password form content.
 *
 * @since 1.0.0
 *
 * @param int|WP_Post $post Optional. Post ID or WP_Post object. Default is global $post.
 * @return string HTML content for password form for password protected post.
 */
function get_the_password_form_recaptcha( $post = 0 ) {
    $post   = get_post( $post );
    $label  = 'pwbox-' . ( empty( $post->ID ) ? rand() : $post->ID );
    $output = '<form id="post-password-form" action="' . esc_url( site_url( 'wp-loginpp.php?action=postpass', 'login_post' ) ) . '" class="post-password-form" method="post">
    <p>' . __( 'This content is password protected. To view it please enter your password below:' ) . '</p>
    <p><label for="' . $label . '">' . __( 'Password:' ) . 
    ' <input name="post_password" id="' . $label . '" type="password" size="20" /></label> <button class="g-recaptcha" data-sitekey="XXX" data-callback="onSubmit" data-action="POST_PASSWORD_FORM">' .
    esc_attr_x( 'Enter', 'post password form' ) . 
    '</button></p><p><input type="hidden" name="g-recaptcha-action" value="POST_PASSWORD_FORM"></p></form>';

    /**
     * Filters the HTML output for the protected post password form.
     *
     * If modifying the password field, please note that the core database schema
     * limits the password field to 20 characters regardless of the value of the
     * size attribute in the form input.
     *
     * @since 2.7.0
     *
     * @param string $output The password form HTML output.
     */
    return $output;
}
add_filter( 'the_password_form', 'get_the_password_form_recaptcha' );

【要編集】data-sitekey="XXX"のXXXは、事前に取得したreCAPTCHAサイトキーを指定して下さい。外部に公開されるため、間違ってもシークレットキーを指定しないようにご注意下さい。

【要注意】ログインフォームのアクション送信先のURLに
wp-login.phpを指定し、SiteGuardプラグインのログインページ変更機能でログインページURLを変更している場合、変更後のURLが自動的に代入されます。このため、外部から変更後のログインページURLが参照できる状態になるため、本記事ではwp-loginpp.phpと言うファイルを指定しています。ログインページ変更機能を有効にしていない場合も、サーバサイドでreCAPTCHAのスコア解釈を行うため、wp-loginpp.phpファイルの指定が必要です。

この時点で、ログインフォームを表示すると画面の右下に、reCAPTCHAの保護中アイコンが表示されます。

スクリーンショット 2020-10-27 16.13.56.png

※画像は一部マスク処理しています。

また、reCAPTCHA管理コンソールを表示すると、サイトからのトラフィックが集計されていることが確認できます。(反映までタイムラグがあります)

スクリーンショット 2020-10-27 16.18.45.png

しかし、サーバ側でスコアの判定処理を記述していないため、このままではチャレンジ結果に応じた処理が行えません。

スコアの解釈

本記事では、スコアの解釈をwp-loginpp.phpファイルを作成して、処理しています。
wp-login.phpの処理にフィルターフックを設けて処理する方法も考えましたが、「図解『wp-login.php』! WordPressのユーザ認証を極める!」1の参考記事のフローを見る限り、パスワードで保護された投稿の認証フローへ適切に挟み込めるフックが見当たりませんでした。

また、WordPress 4.7から追加されたpost_password_requiredフィルタを使い、スコア解釈の判定処理を行う方法も検討しましたが、フック関数のためか、ブラウザからのPOSTリクエストを受け取れず、reCAPTCHAのスコア検証が行えませんでした。余談となりますが、post_password_requiredフィルタはとても便利で、参考記事2で説明されている通り、パスワード保護ページに複数のパスワードを設定する事などが可能です。

wp-loginpp.php

作成したwp-loginpp.phpファイルは下記の通りです。
WordPressインストールディレクトリ(wp-login.phpと同じディレクトリ)に配置します。

wp-loginpp.php

// WordPress API
require_once ( dirname(__FILE__) . '/wp-load.php' );

// SiteGuardプラグインのフィルタ無効化
global $siteguard_rename_login;
remove_filter( 'login_init', array( $siteguard_rename_login, 'handler_login_init' ), 10 );

//
// reCAPTCHA判定処理
// 出典:https://www.webdesignleaves.com/pr/plugins/google_recaptcha.php
//

// シークレットキー
$secretKey = 'YYY';

// 認証アクション
$wp_action = isset( $_GET[ 'action' ] ) ? esc_html($_GET[ 'action' ]) : NULL;

// reCAPTCHA トークン
$token = isset( $_POST[ 'g-recaptcha-response' ] ) ? esc_html($_POST[ 'g-recaptcha-response' ]) : NULL;
// reCAPTCHA アクション名 
$action = isset( $_POST[ 'g-recaptcha-action' ] ) ? esc_html($_POST[ 'g-recaptcha-action' ]) : NULL;

if ( $token && $action && $wp_action === 'postpass' ) {
    // cURL セッションを初期化
    $ch = curl_init();
    // curl_setopt() により転送時のオプションを設定
    // API URL の指定
    curl_setopt( $ch, CURLOPT_URL, "https://www.google.com/recaptcha/api/siteverify" );
    // POST メソッドを使う
    curl_setopt( $ch, CURLOPT_POST, true );
    // API パラメータの指定
    curl_setopt( $ch, CURLOPT_POSTFIELDS, http_build_query( array(
      'secret'   => $secretKey,   //シークレットキー
      'response' => $token        //トークン
    ) ) );
    // curl_execの返り値を文字列にする
    curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
    // 転送を実行してレスポンスを $api_response に格納
    $api_response = curl_exec( $ch );
    // セッションを終了
    curl_close( $ch );

    // レスポンスの $json(JSON形式)をデコード
    $result = json_decode( $api_response );

    // 結果の判定処理
    if( isset($result->success) && $result->success && 
        isset($result->action) && $result->action === $action && 
        $result->score >= 0.5
    ) {
        // success が true でアクション名が一致し、スコアが 0.5 以上の場合は合格

        // wp_login.phpをロード
        require_once ( dirname(__FILE__) . '/wp-login.php' );
    } else {
        // ログインエラーページへリダイレクト
        wp_safe_redirect( esc_url( site_url( 'login-error' ) ) );
    }
} else {
    // ログインエラーページへリダイレクト
    wp_safe_redirect( esc_url( site_url( 'login-error' ) ) );
}

【要編集】$secretKey = 'YYY';のYYYは、事前に取得したreCAPTCHAシークレットキーを指定して下さい。

プログラム中のコメント(出典:)にもある通り、reCAPTCHA判定処理は「Google reCAPTCHA の使い方(v2/v3)」3の参考記事より、ほぼコピーして作成しております。参考記事では、シークレットキーは外部ファイルに格納して、requireする実装になっています。

このプログラムは、大まかに下記の処理を行っています。

  • require_onceでWordPress APIを利用可能にします。
  • SiteGuardプラグインのログインページ変更機能で使用されるフィルタ 'login_init' を無効化します。有効化されている場合、後述のwp-login.phpをロードした後、URLが無効化されてしまい、404エラーとなります。リダイレクトではなく、サーバーサイドによるrequire_onceによるwp-login.phpの読み込みでもフィルタの対象になります。なお、無効化したフィルタは、次回URL読み込み時には自動で有効化されていたため、本プログラムでは有効化の処理は記述していません。
  • URLのGETのactionがpostpass(パスワード保護ページの認証)かを判定します。この判定がないと、不正なURLを指定された場合にログイン画面が表示される恐れがあります。
  • reCAPTCHAのスコア判定処理を実行します。ここでは「success が true でアクション名(ログインフォームの隠し要素の値)が一致し、スコアが 0.5 以上の場合は合格」としています。アクション名の比較はPOST値とAPI戻り値を比較しているだけのため、POST値を詐称されれば意味がないかも知れません。気休め程度に割り切っています。
  • 合格ではない場合は全て、固定ページのsite_url/login-errorへリダイレクトしています。

エラーページ

WordPressの固定ページで、エラーページを作成します。
URLはsite_url(サイトのベースURL)/login-errorですが、前述のwp-loginpp.phpのリダイレクト先を変更すれば、どのようなURLでも対応可能です。
今回の記事では、reCAPTCHAエラー時の詳細なメッセージを出力する処理は実装せず、単純にreCAPTCHAエラーとだけ表示しています。

スクリーンショット 2020-11-09 21.48.14.png

なお、本エラー画面は、ログインフォームを経由せずに直接site_url/wp-loginpp.phpへアクセスした場合にも表示されます。これにより、SiteGuardプラグインでログインページURLを隠しながらも、パスワード保護記事のログイン認証を実行させる事が可能です。

その他の考慮点

  • ブラウザのJavaScriptが無効化されている場合、無条件でreCAPTCHAエラーとなります。JavaScriptが無効化されている場合は、有効化するようにアナウンスを設けるのがフレンドリーかも知れません。
  • reCAPTCHA v3による判定に合格しなかった場合は、v2のチャレンジを表示する仕組みを考えても良いかも知れません。

終わりに

今回はWordPressのパスワード保護記事のログイン認証にreCAPTCHA v3を追加する方法を記載しましたが、これがベストプラクティスであるとは残念ながら思っていません。よりより方法がありましたら、ご指摘いただければ幸いです。

参考

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1