PHPで非SSLの画像をキャッシュあるいはプロキシ的に表示して警告を回避する方法
「このサイトへの接続は完全には保護されていません」
…いやなメッセージですよねぇw
SSLに対応しているページ内で、外部の非SSLの画像を表示した場合など、ブラウザのアドレスバーにこの警告が表示されてしまいます。
「このサイトへの接続は完全には保護されていません」が表示される原因
SSLに対応したWebページ内で下記のように非SSLの画像を埋め込んだ場合に警告が表示されます。
<img src="http://example.com/sample.jpg">
scriptタグやiframeタグ等でも同じで、https対応ページからhttpのページを埋め込もうとすると「このサイトへの接続は完全には保護されていません」と出ます。
しかも、クリックして詳細を確認すると「このサイトで目にする画像は、悪意のあるユーザーによって差し替えられたものである可能性があります」というかなーり不安になるメッセージが表示されちゃうんですよねぇ。
そもそもSSLに対応していないページの場合は「セキュリティ保護なし」と表示され、その詳細には「このサイトではパスワードやクレジットカードの情報を入力しないように」的な説明が出ますが、なんとなくこっちのほうがまだマシに見えるから困りますw
前者の場合(SSLページで非SSL画像を埋め込んだ場合)はクラックでも受けているかのような悪印象があります。
実際、「PHPでリンク先のOGPタグを読み取りサムネイルを表示する方法」で書いたように、掲示板のようなシステムをPHPで作ったとして、そのとき本文にURLを入力された際に、リンク先の画像を読み取ってサムネイルを表示する、みたいな機能を組み込んだ場合、このメッセージの懸念は現実のモノとなります。
運営者の予期せぬ非SSLの画像を埋め込まれる可能性があり、その通信だけは保護されないのですから。
さて、ではどうやって対処していこうかって話に移ります。
非SSL画像をキャッシュすることで警告を回避する
そもそも自サイトがSSLに対応していて、画像ファイルも自サイト内にあればそのまま表示するだけでSSL通信になるわけです。
じゃあリモートの画像をいったん自サイトにキャッシュしてから表示すればいいよね、っていう話。
例えばPHPならコレだけ。
$img = file_get_contents('http://example.com/sample.jpg');
file_put_contents('cache/sample.jpg', $img);
file_get_contentsでリモートの非SSL画像を読み込んで自サイト内へ保存。あとはそのキャッシュ画像をimgタグに埋め込めば良いです。
…ただなぁ。ファイル名決めるのが面倒くさくないですか?
どういうジャンルのサイトを作るかにもよりますが、仮に先述したようにPHPで掲示板を作っているとでもしましょうか。
そして書き込みの本文中にURLを入力され、そのリンク先のページにOGPが設定されていた場合、掲示板の本文中にサムネイルを表示する、みたいな流れ。
文章にするとややこしいですかね?
「PHPでリンク先のOGPタグを読み取りサムネイルを表示する方法」と同じ例を挙げますが、要は本文中に↓ここで買いました https://item.rakuten.co.jp/nitori/6620531-/みたいな文章を入力されたときに、
↓ここで買いました
という表示に変換する仕組みです。便利でしょ。
そのとき、埋め込む画像URLが非SSLだったりするので、それをキャッシュして表示しよう、というのが前述の例ですが、どんなURLを指定されるかわからない以上、キャッシュファイル名を決めるのが面倒だなぁーと。
入力されたURLの画像ファイル名部分をそのままキャッシュファイル名にするのは重複する可能性が高すぎるのでありえないし、掲示板の番号やスレッド番号、投稿番号+連番、みたいな、重複しないキャッシュファイル名を考えるのが面倒です。
まぁ面倒がっても仕方ないから実装するんですけど、その後しばらくしてからそのキャッシュファイルを消すタイミングとか、消し忘れとかあって、だんだん更に億劫になってきました。はい、これは実体験の話です。
PHPをプロキシーにして非SSL画像を出力する
だったら、わざわざ画像ファイルをキャッシュせず、リアルタイムでそのまま右から左へ出力すれば良いのでは?と考えて試しに作ったのがこちら。
<?php
header('Content-Type: image/jpeg');
readfile($_GET['url']);
?>
これを img.php とでも付けて保存し、HTMLでは↓こんなふうに埋め込みます。
<img src="img.php?url=http://example.com/sample.jpg">
するとあーら不思議。http://example.com/sample.jpgはSSLに非対応なのに、いったん自サイトにある img.php が読み込んでから出力するため、SSLで表示されます。
ただ、このままだと画像がキャッシュされなくて通信量が膨大になってしまいます。
PHPのデフォルト設定ではたしかキャッシュ無効だったハズなので。
ということで、30日間のキャッシュを追加したのがこちら。
<?php
$url = $_GET['url'];
//キャッシュ設定
$expires = (60*60*24)*30;
header('Pragma:cache');
header('Cache-Control: private, max-age='.$expires);
$ext = strtolower(getExt($url));
switch ($ext) {
case '':
case 'jpg':
header('Content-Type: image/jpeg');
break;
default:
header('Content-Type: image/'.$ext);
}
readfile($url);
/************************************************
* 拡張子の取得
************************************************/
function getExt($filename)
{
if (($pos = strrpos($filename, '.')) !== false) {
return substr($filename, $pos+1);
} else {
return '';
}
}
?>
ついでにJPEG以外(png/gif/webp等)でも良いように、拡張子次第で Content-Type を切り替えるコードも入れておきました。
尚、この記事に限らず、当ブログで公開しているコード類はなるべくパッと見でわかるようエラーチェックを極力排除してシンプルにしていますので、実際に導入する際は $_GET['url'] の入力値チェックなどしっかりするようにしてくださいね。
まとめ
SSLページで非SSL画像を埋め込むと「このサイトへの接続は完全には保護されていません」という嫌らしい警告が出てしまうので対応してみました。
対応方法としては2種類。
- リモートの画像をキャッシュしてから表示する
- リモートの画像を右から左へ出力する
そもそも、リンク先の画像がSSLに対応していればhttp://をhttps://へ書き換えれば良いだけなんですけどねー。
具体例としてはshopify等が挙げられます。Shopifyはネットショップを開設するためのプラットフォームですが、最近利用するお店が増えたように思います。
当然のようにOGPに対応しており、商品写真のサムネイルを使ってくれ~、というのを感じさせるのですが、お店はSSLに対応しているのにOGPタグの中では非SSL画像を指定している、なんてパターンもあるんですよ。
こんなときはhttp://をhttps://へ書き換えるだけで対応できてしまいます。
すべてがこういう簡単な対応で済めば良いのですが、まだまだSSL非対応のサイトが多い現状、小手先の回避方法ではありますが、画像キャッシュや、自前のなんちゃってプロキシー等で対応するしかないかなぁーと思います。
では、そんな感じで、この記事が非SSLの画像表示で困っている方の参考になれば幸いです。