05 Setting up a captcha

簡介

因為任何人在BLOG上都可以對文章發表評論,所以,我們應該要多保護認證機制,以避免產生多餘的垃圾訊息。用一個最簡單的方式來保護就是新增一組captcha圖示


Generating the captcha image

我們即將開始展示如何方便地使用Play來產生一個captcha圖示。基本上,我們只要運用一個action,但它會返回一個二進制串流。自於Play是一個full-stack web framework,我們可以用簡單的play.libs.Images來產生驗證碼圖示,然後把它寫入HTTP Response。一如往常,我們在Application controller新增一組captcha action:

public static void captcha() { Images.Captcha captcha = Images.captcha(); renderBinary(captcha); }

注意,我們可以通過captcha物件直接到renderBinary()方法,是因為Images.Captcha class實作了java.io.InputStream,但別忘了import play.libs.*,現在,新增一個URL路徑在 /yabe/conf/routes 吧!!

GET /captcha Application.captcha

打開您的瀏覽器http://localhost:9000/captcha,你會發現.........




它應該會產生一組隨機的驗證碼。


How do we manage the state?

到目前為止還是很容易,但是接下來就是較複雜的部分。為了驗證captcha,我們必須保存文字寫入隨機驗證碼圖示的地方,然後檢查它在表單所提交的時間。當然,我們可以只把文字在圖示產生的時間下用至使用者的session當中,然後檢索它。但這個辦法有兩個缺點:

一、Play session會被儲存為一組cookie。它解決了很多問題,但在架構上還是有很大的影響。資料寫入session cookie(使用者不能修改它們),但因為沒有加密。如果我們寫的驗證碼至session中,任何人都可以很容易地破解session cookie所讀取的驗證碼。

二、要記住,Play是一個stateless framework。我們想要管理一個stateless的方式,在這種情況下,會發生什麼事??如果一個使用者同時打開兩組不同的BLOG網頁也有兩組不同的captcha圖示??我們要如何追察每個表單的captcha圖示。

因此,為了解決這個 問題,我們需要做兩件事情。我們將驗證碼金鑰儲存在Server端。因為資料生命周期是短暫的,我們可以方便地使用Play Cache。而且由於緩存的資料有一定的生命周期,時間會增加一個安全機制(例如,一個驗證碼生命周期只有10mn)。然後,解決該程式碼之後,我們需要產生一個唯一ID。這種唯一ID將被增加到每個表單作為一個隱藏欄位以及產生驗證碼圖示。緊接著,修改captcha action的方法:

public static void captcha(String id) { Images.Captcha captcha = Images.captcha(); String code = captcha.getText("#E4EAFD"); Cache.set(id, code, "10mn"); renderBinary(captcha); }

注意,getText()方法接受任何顏色參數的設定。它會用所設定的顏色來繪製文字,但最重要的是別忘了 import play.cache.*


Adding the captcha image to the comment form

現在,我們將產生一組唯一ID在顯示評論表單之前。然後,我們將修改HTML表單將captcha圖示使用這組ID,並增加這組ID至隱藏欄位中。讓我們重新撰寫Application.show() action:

public static void show(Long id) { Post post = Post.findById(id); String randomID = Codec.UUID(); render(post, randomID); }

現在表單呈現方式 /yable/app/views/Application/show.html模版:

... <p> <label for="content">Your message: </label> <textarea name="content" id="content">${params.content}</textarea> </p> <p> <label for="code">Please type the code below: </label> <img src="@{Application.captcha(randomID)}" /> <br /> <input type="text" name="code" id="code" size="18" value="" /> <input type="hidden" name="randomID" value="${randomID}" /> </p> <p> <input type="submit" value="Submit your comment" /> </p> ...

完成之後就會發現評論表單多了驗證圖示嘍!!!




Validating the captcha

現在,我們只需要驗證captcha。因此,我們可以在postComment action之中檢索Cache出來的程式碼,最後,並提交程式碼。事實上,也沒有那麼困難,讓我們修改postComment action:

public static void postComment( Long postId, @Required(message="Author is required") String author, @Required(message="A message is required") String content, @Required(message="Please type the code") String code, String randomID) { Post post = Post.findById(postId); validation.equals( code, Cache.get(randomID) ).message("Invalid code. Please type it again"); if(validation.hasErrors()) { render("Application/show.html", post, randomID); } post.addComment(author, content); flash.success("Thanks for posting %s", author); Cache.delete(randomID); show(postId); }

因為,我們現在有很多種錯誤訊息,所以,必須再次修改show.html模版顯示錯誤訊息的方式(目前,一次只顯示一條錯誤訊息):

.. #{ifErrors} <p class="error"> ${errors[0]} </p> #{/ifErrors} ...


通常,對於較複雜的表單來說,錯誤訊息這種方式是不納入管理,並且應該在訊息輸入欄位之外並顯示錯誤在相對應的欄位上。


檢查一下captcha是否正常運作。




Comments