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

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 '注册'