PHPメモ037:cURLでHTTPSなサイトにアクセスする
PHPで外部のWebサービスを利用するため、POSTメソッドのHTTPリクエストを送信する処理について調べた。単純にWebコンテンツを持ってくるだけなら file_get_contents() でもいいのだが、今回はWEBサービスを利用するためのリクエストで、レスポンスのステータスコードによって処理を適宜分ける必要があるかもしれないので file_get_contents() は使わないことにした。
#file_get_contents() でもステータスコードを取ることはできるようだ。(記事末の参考サイトを参照)
file_get_contents() 以外の方法を探して下記のサイトを見つけた。
#"php post curl"でググるとこれ以外にもたくさん参考になるサイトが出てくる。
cURLが使える環境だったので「cURL版」をベースに、GETは必要ないので不要な部分を削った関数を作成。こんな感じ。
function requestByPost($url, $params = array(), $headers = array()) { $ch = curl_init($url); curl_setopt($ch, CURLOPT_POST, TRUE); curl_setopt($ch, CURLOPT_POSTFIELDS, $params); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); curl_setopt($ch, CURLOPT_TIMEOUT, 0); // いつまでも待つ $res_data = curl_exec($ch); $res_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); if (preg_match('/^20.$/', $res_code) !== 1) { // ステータスコードが200番台以外の場合 $res_data = NULL; } if (curl_errno($ch) != 0) { log_info('curl error:[' . curl_errno($ch) .'] '. curl_error($ch)); } curl_close($ch); return $res_data; }
これを利用して下記のようなコードによりHTTPSでBASIC認証ありのリクエストを送ってみた。接続先は他社Webサービスのテスト環境。URLは適当に書き換えてある。
function requestForService($id, $password, $name) { $url = 'https://api.test.service.jp/create'; // $url = 'https://api_test.service.jp/create'; // 追記。これならエラーにならない。下の追記を参照。 $basic_auth_id = 'hogehoge'; $basic_auth_pass = 'hoge1234'; // POSTパラメータ $req_params = array( 'id' => $id, 'password' => $password, 'name' => $name ); // HTTPリクエストヘッダ $str_basic_auth = $basic_auth_id . ':' . $basic_auth_pass; $req_headers = array('Authorization: Basic ' . base64_encode($str_basic_auth)); // リクエストを送信してID発行を要求 $res_data = requestByPost($url, $req_params, $req_headers); return $res_data; }
しかし、うまくいかなかった。ステータスコードを調べると "0"。
"0" というのはありえないので、HTTPで通信する以前でエラーになっているようだ。
curl_errno()、curl_error() によると
[51] SSL: certificate subject name '*.service.jp' does not match target host name 'api.test.service.jp'
サーバーの証明書のエラー?しかし "*.service.jp" ならば "api.test.service.jp" とマッチするのでは?よくわからない。
とりあえず
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
を追加してサーバー証明書の検証をしないようにしたところ、レスポンスが帰ってきてステータスコードは200番台、想定どおりの動きになった。
しかし本番では証明書の検証はしないというのは、まずいような。
エラーの原因はサーバ側かクライアント側か。
自分が作っている方がこちらがHTTPクライアントである。ということWEBブラウザと同様にクライアント証明書が必要じゃないか、という気がして調べた。
cURLのサイトに証明書が置いてあるので、それを設定するということが書いてあったので同じようにやってみた。証明書を持ってきてサーバ上に置き、下記のコードを追加。
curl_setopt($curl, CURLOPT_CAINFO, '/var/www/ca/cacert.pem');
しかし、これでも同じエラーが出た。
うーん。原因はこちら側(クライアント側)ではなくサーバー側の証明書なんだろうか?
2014/03/28 22:30追記
エラーが解消した。Webサービスの運営に質問したら「ホスト名を "api_test.service.jp" でもアクセスできます。そっちならエラーにならないかも。」と回答があったので、そのように変更したところ、エラーが発生しなくなった。上記のような curl_setopt() による設定による証明書関連の設定していない。つまりクライアント証明書を設定しておらず、サーバー証明書の検証はしているはず。
エラーメッセージにある "*.service.jp" は "api.test.service.jp" とはマッチしないが、"api_test.service.jp" とはマッチするということだろうか。
しかし、ホスト名にアンダースコアは使えないはずだが。まあ試験環境だし、そもそも他社のサービスだし、深く考えないでおこおう^^;
あと、こちらのサーバーに "/etc/pki/CA/cacert.pem" があった。cURLではデフォルトでこの証明書をクライアント証明書として使っているのだろうか?
問題は解決したが、またわからないことが出てきた。
参考サイト:
ウェブル@soraiyさんのPHP で凄く簡単に GET/POST 送信ができる関数を勝手に改良した | zaru blog
PHPのcURL関数について:
PHP: 基本的な curl の使用法 - Manual
PHP: cURL 関数 - Manual
cURLのサイト:
curl and libcurl
cURL - Extract CA Certs from Mozilla:証明書が置いてある
cURLについて参考にしたサイト:
cURL | PHP プログラミング解説
メモ: [PHP][cURL] cURLでSSL(https)のCA証明書警告の回避や設定
【php】curlでSSL通信をCA証明書のチェックありで at softelメモ
PHP - ローカルサーバからWebサービスAPIへSSL接続時に出るcurlのエラー対処 - Qiita
サーバのSSL CA(認証局)証明書が古くてcurl がエラーになる件 - うまい棒blog
cURL - Manual (マニュアル日本語訳)
証明書について参考にしたサイト:
SSL通信時発生する証明書エラーとその仕組みを理解する - Qiita
OpenSSL証明書操作/VeriSign - # cat /var/log/stereocat | tail -n3
デジタル証明書の形式
証明書のファイル形式について
その他参考にしたサイト:
PHP の file_get_contents でステータスコードを取得する
« ファイルダウンロードによるXSS その3 | Main | Deferredの復習 »
「PHP」カテゴリの記事
- LaravelでHello World とか諸々(2015.05.13)
- Laravel4のインストール(2015.05.06)
- PHPメモ051:includeとrequireの使い分け(2015.06.19)
- CakePHPのインストール(2015.06.14)
- PHPからPDOでPostgreSQLに接続する(2015.06.09)
TrackBack
TrackBack URL for this entry:
http://app.cocolog-nifty.com/t/trackback/26461/59366687
Listed below are links to weblogs that reference PHPメモ037:cURLでHTTPSなサイトにアクセスする:
Comments