Ruby on Rails で CAPTCHA を行う

 長年メンテナンスしてきた幾多の掲示CGI がスパムに犯されているのを目の当たりにしているので、最近は CAPTHCA に大注目の私なのですが、今日は、Ruby on Rails による Web アプリケーションに割と簡単に CAPTCHA を導入する方法を紹介します。

 使うのは Ruby/CAPTCHA というライブラリです。個人的には、それほど洗練されたライブラリだとは思っていないのですが、「とりあえず CAPTCHA をつけたい」と言った場合には必要十分です。

 まず、Ruby/GD をインストールします。どこぞの Blog で Ruby/CAPTHCA は RMagick に依存すると書いてありましたが、Ruby/GD が正解です。インストールの仕方は以下を参照してください。「もう Ruby/GD は入ってるぜ!」という方は、freetype をサポートするビルドオプションをつけてインストールしたかを確認してください。

Ruby/GD の導入

 つぎに Ruby/CAPTCHA をインストールします。これは gem でふつうに入ります。

# gem install captcha

 次に Controller を編集します(以下はクラス宣言部等は省略しています)。

  gem 'ruby-gd'
  gem 'captcha'
  require 'captcha'

  def captcha
    w = CAPTCHA::Web.new
    # フォントの指定
    w.font = "/usr/share/fonts/bitstream-vera/VeraBd.ttf"
    # 生成した CAPTCHA 用画像の保存先
    w.image_dir = "/path/to/rails/public/images/captcha"
    w.font_size = 23
    w.rotation = 28
    w.x_spacing = 6
    w.y_wiggle = 22

    @image = w.file_name
    @digest = w.digest
    @image_width = w.image.width
    @image_height = w.image.height

    if params[:digest] && params[:key]
      if CAPTCHA::Web.is_valid(params[:key], params[:digest])
        session[:captcha] = true
        redirect_to :action => "new"
      else
        flash[:notice] = "あなたは、本当に人間ですか?"
      end
    end
  end

 最後に view を編集します。

<% form_tag :action => 'captcha' do %>
  <p><%= image_tag "captcha/#{@image}", :size => "#{@image_width}x#{@image_height}", :alt => "" %></p>
  <input type="text" name="key" value="" />
  <input type="hidden" name="digest" value="<%= @digest %>" />
  <%= submit_tag "認証" %>
<% end %>

 これで OK です。

 この方法の難点をあげますと

  1. 画像ファイルが増え続ける。
  2. CAPTCHA 用画像の種が丸見え(HTMLソースをのぞくと見えるという意味)でちょっと怖い。
  3. 視覚障害者が使えない。

1 に関しては、Ruby/CAPTHCA でも clean というメソッドで古い画像ファイルを消すことができるのですが、SAFE レベルが 2 以上だと動かないので私は使っていません。ただ、今後 FIX されることを期待して以下のようにオーバーライドして使っています。

module CAPTCHA
  class Web
    def clean
      Dir.foreach(@image_dir) do |entry|
        next if entry !~ /\.png$/
        entry.untaint
        if Time.now - File.stat(File.join(@image_dir, entry)).mtime > @clean_up_interval
          File.delete(File.join(@image_dir, entry))
        end
      end
    end
  end #class Web
end #module CAPTCHA

これを用いる場合は file_name メソッドが呼ばれる瞬間に画像ファイルが生成されるのでその直前に以下のように追加します。

# 以下で指定した秒数以前のファイルは削除する
w.clean_up_interval = 600
w.clean

2 に関しては、メッセージダイジェストから CAPTCHA 文字列を推測するのは容易なことではないし、やったとしても恐らく人が手で打つより遅いでしょうから、CAPTCHA の役目が著しく損なわれることはないでしょう(CAPTHCA 画像が OCR で突破されるのと同じぐらいのリスクかな?)。
3 に関してはなすすべなしです。以前紹介した JCapthca なら音声 CAPTCHA もできるようなので、そういった別のソリューションを用いるしかないと思います。