在网站上注册用户时,网站会生成一张图片验证码,要求用户输入正确的验证码才能够完成注册。这套机制是为了 防止机器人注册大量的水号帐户。

Captcha 示例

如果你使用Rails开发的话,可以试试simple-captcha这个gem,此gem提供了许多API帮助你开发一套图片 captcha。但是我们现在想在Rails环境下手动打造一套图片captcha,以便理解更多关于captcha的细节。

Captcha流程

先是生成一个随机字符串label和一个uuid,字符串长度4-6个就好了,太长的话不方便用户输入,太短的话容易被 机器人破解,将uuid发送给图片生成器生成含有label字符的图片,最后验证时,将uuid和用户输入的字符串发送到 captcha验证器进行验证。在验证器里主要是通过uuid从数据库里找出用于生成图片的label,然后使用此 label和用户输入的文本进行对比。

将过程抽象为Activity

这里介绍我写的一个叫作Activity的类,此类专用于对过程进行抽象,帮助我们从面向过程的角度来解决一些编程 问题。

   class Activity
     attr_reader :data
	 
     def self.<< data
       new(data).call
     end
	 
    def initialize(data)
      @data = data
    end
	
    def call
      ''
    end
	
   end
   

生成label和uuid

我用一个叫captcha_labels的表保存label和uuid, 在Rails中captcha_labels对应的model是CaptchaLabel

	
	  class CreatingCaptchaLabel < Activity
        def call
		  label = SecureRandom.hex(3)
		  uuid = SecureRandom.uuid
		  
		 CaptchaLabel.create(label: label, uuid: uuid)
		end
	  end
	  
	  captcha_label = CreatingCaptchaLabel << {}
	

生成图片

需要使用ImageMagickmini_magick

     class CreatingCaptchaImage < Activity
	   attr_reader :label, :image
       attr_reader :pointsize, :kerning, :undercolor, :noise
  
       def initialize(data = {})
         super
         @pointsize = data[:pointsize] || 20
         @kerning = data[:kerning] || 1
         @undercolor = data[:undercolor] || 'lightgray'
         @noise = data[:noise] || 'Poisson'
         @label = data[:label]
         @command_options = []
         @image = MiniMagick::Image.new('captcha.jpg')
       end
	   
       def call
        add_command_option '-pointsize', pointsize
        add_command_option '-kerning', kerning
        add_command_option '+noise'
        add_command_option '-undercolor', undercolor
		
        MiniMagick::Image.read(convert_image)
       end
	   
       private
	   
       def add_command_option(option, value = nil)
         value = value.nil? ? send(option.sub(/[-+]/, '')) : value
         @command_options << "#{option.to_s} #{value}"
       end
	   
       def command_options
         @command_options.join(' ')
       end

       def convert_image
        image.run_command "convert #{command_options} label:#{label} jpg:-"
       end
     end
	 
	 captcha_image = CreatingCaptchaImage << {label: '3fd712'}
	

图片路径

首先增加路由, 在routes.rb文件中加入下面的代码

	 get 'captcha' => 'captcha#show', :as => 'captcha'
	

然后建立控制器,rails g controller captcha, 此命令会生成 CaptchaController,captcha_controller.rb文件的代 码如下,

	 class CaptchaController < ApplicationController
       def show
         captcha = CaptchaLabel.where(uuid: params[:uuid]).first
         image = CreatingCaptchaImage << {label: captcha.label}
         send_data image.to_blob, type: 'image/jpg', disposition: 'inline'
       end
     end
	

在html里写入<img src=’/captcha?uuid=:uuid’>就能够得到一张验证码图片,:uuid用数据库里实际存在的值代替即可。

集成验证码

假设我们在用户注册时需要使用验证码来甄别人类和机器。首先,增加用于注册的路由,

    get 'regist' => 'regist#new', :as => 'regist'
    post 'regist' => 'regist#create', :as => 'regist'
	

生成用户注册控制器,rails g controller regist,生成RegistController,其内容如下(省去了和captcha无关的内容),

     class RegistController < ApplicationController
	  def new
	    @captcha = CreatingCatchaLable << {}
	  end

      def create
	    captcha = CaptchaLabel.where(uuid: params[:captcha_uuid]).first
		if captcha.label.upcase == params[:captcha_label].upcase
		  render :text => '注册成功'
		else
		  render :text => '验证码错误'
		end
	  end
    end
	

视图文件view/reigst/new.html.haml的内容如下(省去了和captcha无关的内容),

   = form_tag regist_url, :method => 'post' do
     %input{type:'text', value:@captcha.uuid, name:'captcha_uuid', style:'display:none;'}
	 %input{type:'text', name:'captcha_label'}
	 %img{src:"#{captcha_url}?uuid=#{@captcha.uuid}"}
	 %submit_tag '注册'