整合 reCAPTCHA 到你的專案上,搭配 rails、devise
reCAPTCHA 是 Google 在 2009年收購一家叫 CAPTCHA 的公司,其服務的目的是為了辨認機器人與防止廣告垃圾訊息氾濫。
reCAPTCHA 問題的所需的文字圖片,首先會由 reCAPTCHA 計畫網站利用 Javascript API 取得,在終端使用者回答問題後,伺服器再連回 reCAPTCHA 計畫的主機驗證使用者的輸入是否正確 - wiki。
reCAPTCHA 大致分為 3 種:
v2 Checkbox:「我不是機器人」核取方塊,網站上最常看到的一種,選取圖片或文字來做驗證。
v2 Invisible:隱形 reCAPTCHA 標記,在發送之前先在背景依照使用者滑鼠軌跡、瀏覽器資訊、Cookie等…判斷是否為機器人,如果判斷為是,則會跳出 v2 Checkbox 的驗證方式來做第二次驗證。
v3:以分數驗證要求,驗證完在後端可以得到驗證分數,區間為 0.0 ~ 1.0,分數越低則機器人的可能性越高,可以自行以分數高低,來決定是否要進行第二次驗證,例如:寄送 email、簡訊驗證或是使用 v2 Checkbox …等。
本次會實作以下幾個項目:
- 實作
v2 Checkbox
在註冊帳號頁面 - 實作
reCAPTCHA v3
在登入頁面 - 實作
reCAPTCHA v3
fallback validate
環境
- ruby 2.5.0
- Rails 5.2.3
- sqlite3 3.22.0
先準備一個擁有 Devise 登入頁面的專案:
recaptcha-with-rails-devise-demo
$ git clone https://github.com/treekey999/recaptcha-with-rails-devise-demo.git
$ cd recaptcha-with-rails-devise-demo
$ git checkout prepare_for_start # 切到已準備好環境的分支
$ git checkout -b my_practice # 在此建立一個 branch 並 checkout 過去
$ bundle
$ yarn
$ rails db:migrate
$ rails s
主要先準備了幾個前置作業:
- devise setup:新增 User model
- devise-bootstrap-views:簡易版 devise bootstrap layout
- dotenv rails:管理 env 變數,存放敏感資訊
- rails scaffold Todos title:string description: string user:reference
每一步驟都可以在 git commit 中找的到。
實作 v2 Checkbox 在註冊帳號頁面
1. 註冊取得 site_key
& secret_key
site key:前端用來跟 google recaptcha 取得 recaptcha token。
secret key:後端用來驗證從前端來的 recaptcha token。
註冊: https://www.google.com/u/1/recaptcha/admin/create
回到專案下,把 .env
複製一份並命名為 .env.local
,由於已經把 .env.local
加到 .gitignore
所以不會把敏感資訊推到 github 上。
剛剛註冊得到得 site_key
、secret_key
,填到 v2 對應的 key 上,這時候你在 rails 就可以用 ENV["RECAPTCHA_V2_CHECKBOX_SITE_KEY"]
來取得內容。
RECAPTCHA_V2_CHECKBOX_SITE_KEY="your_site_key......."
RECAPTCHA_V2_CHECKBOX_SECRET_KEY="your_secret_key......"
RECAPTCHA_V3_SITE_KEY=""
RECAPTCHA_V3_SECRET_KEY=""
2. 安裝 gem
在 Gemfile
加上這一段,並做 $ bundle
。
https://github.com/ambethia/recaptcha
gem 'recaptcha', git: "https://github.com/ambethia/recaptcha.git"
3. overwrite devise user controller
匯出 devise controller。
$ rails g devise:controllers users
修改 routes.rb
告訴 devise user/registrations
controller 要被覆寫。
Rails.application.routes.draw do
root 'todos#index'
resources :todos
devise_for :users, controllers: {
registrations: 'users/registrations',
}
end
4. 新增 before_action
修改 app/controllers/users/registrations_controller.rb
, 在 create 的時候,要做 recaptcha 檢查,如果沒通過就 redirect_to 註冊頁面。
class Users::RegistrationsController < Devise::RegistrationsController
before_action :check_recaptcha_v2, only: [:create]
......
private
def check_recaptcha_v2
valid = verify_recaptcha secret_key: ENV["RECAPTCHA_V2_CHECKBOX_SECRET_KEY"]
if not valid
redirect_to new_user_registration_path
end
end
end
此時如果去註冊帳號,就會發現被檔下來了。
5. 加入 recaptcha_tags
到 register form
修改 app/views/devise/registrations/new.html.erb
24 行, 在 submit 前面加上 recaptcha_tag
。
......
<div class="form-group">
<%= recaptcha_tags site_key: ENV["RECAPTCHA_V2_CHECKBOX_SITE_KEY"] %>
</div>
<div class="form-group">
<%= f.submit t('.sign_up'), class: 'btn btn-primary' %>
</div>
......
如果一切都順利的話,可以看到表單上出現了 recaptcha 按鈕,並可以正確的送出表單 🎉 。
可以看到帶了一個 g-recaptcha-response
的 params。
如果失敗,應該會看到這個畫面,應該檢查一下 ENV
的值有沒有正確、.env.local
有沒有正確 或 recaptcha設定
有沒有加 localhost
到 domain 清單裡面。
實作 reCAPTCHA v3 在登入頁面
1. 註冊 recaptcha 並選擇 v3,加到 .env.local
RECAPTCHA_V2_CHECKBOX_SITE_KEY="your_site_key......."
RECAPTCHA_V2_CHECKBOX_SECRET_KEY="your_secret_key......"
RECAPTCHA_V3_SITE_KEY="your_v3_site_key......"
RECAPTCHA_V3_SECRET_KEY="your_v3_secret_key......"
2. 修改 routes.rb
,覆寫 user/sessions
controller
Rails.application.routes.draw do
root 'todos#index'
resources :todos
devise_for :users, controllers: {
registrations: 'users/registrations',
sessions: 'users/sessions',
}
end
3. user session controller 加入 before_action
修改 app/controllers/users/sessions_controller.rb
。
分數為 0.1
~ 1.0
之間,越低代表越可能是機器人,minimum_score 設為 0.5
,表示低於 0.5
就會回傳 false
。
class Users::SessionsController < Devise::SessionsController
before_action :check_recaptcha_v3, only: [:create]
......
private
def check_recaptcha_v3
valid = verify_recaptcha(
# action:當收到 recaptcha token 的時候要認哪一個名稱來做驗證
# 與前端 form 表單呼應
action: 'login',
minimum_score: 0.5,
secret_key: ENV["RECAPTCHA_V3_SECRET_KEY"],
)
if not valid
redirect_to new_user_registration_path
end
end
end
4. 加入 recaptcha_v3
到 login form
修改 app/views/devise/sessions/new.html.erb
22 行, 在 submit 前面加上 recaptcha_v3
,action
必須跟 controller 驗證端命名一致。
......
<div class="form-group">
<%= recaptcha_v3 action: 'login', site_key: ENV["RECAPTCHA_V3_SITE_KEY"] %>
</div>
<div class="form-group">
<%= f.submit t('.sign_in'), class: 'btn btn-primary' %>
</div>
......
成功的話可以看到出現在該頁右下角的位置。
可以看到帶了一個 g-recaptcha-response
,並有剛剛設置的 action 名稱。
如果失敗,打開 js console 查看錯誤訊息。
5. 加入 v3 驗證失敗的 fallback 驗證
修改 app/controllers/users/sessions_controller.rb
class Users::SessionsController < Devise::SessionsController
before_action :check_recaptcha_v3, only: [:create]
# before_action :configure_sign_in_params, only: [:create]
# GET /resource/sign_in
def new
# 把參數 `verify_v2` 存起來留給 view 使用
@verify_v2 = params[:verify_v2] === 'true'
super
end
......
private
def verify_recaptcha_tags
# 用來驗證 v2 recaptcha_tags (secret_key 不一樣)
verify_recaptcha secret_key: ENV["RECAPTCHA_V2_CHECKBOX_SECRET_KEY"]
end
def check_recaptcha_v3
v3_valid = verify_recaptcha(
# action:當收到 recaptcha token 的時候要認哪一個名稱來做驗證
# 與前端 form 表單呼應
action: 'login',
minimum_score: 0.5,
secret_key: ENV["RECAPTCHA_V3_SECRET_KEY"],
)
v2_valid = verify_recaptcha_tags unless v3_valid
if v3_valid || v2_valid
# 如果驗證成功要額外做什麼事...
else
# 驗證失敗, 導到登入頁面並帶一個 verify_v2=true 的參數
redirect_to new_user_session_path(verify_v2: true)
end
end
end
修改 app/views/devise/sessions/new.html.erb
......
<div class="form-group">
<% if @verify_v2 %>
<%= recaptcha_tags site_key: ENV["RECAPTCHA_V2_CHECKBOX_SITE_KEY"] %>
<% else %>
<%= recaptcha_v3 action: 'login', site_key: ENV["RECAPTCHA_V3_SITE_KEY"] %>
<% end %>
</div>
<div class="form-group">
<%= f.submit t('.sign_in'), class: 'btn btn-primary' %>
</div>
......
由於無法完全模擬出分數很低的情況,所以可以直接在登入網址加上 ?verify_v2=true
,來測試用 v2 checkbox 與用 v3 可不可以成功登入。
模擬機器人瀏覽
打開 js console,將 User agent
改成 Googlebot/2.1
,recaptcha 就會認為這是機器人瀏覽,使用此情況最好是在浮動 IP 的網路上會比較好,recaptcha v3 會記錄你的 ip verify 次數。
但是這也沒辦法完全測試剛剛完成的 fallback 驗證,因為在進行 v2 checkbox 的過程中,他會吐出無窮盡的圖片給你點選。
參考:
https://github.com/ambethia/recaptcha
https://developers.google.com/recaptcha/intro?hl=zh-TW