Play!框架文档翻译:验证码(Captcha)

红薯 发布于 2010/07/13 14:45
阅读 2K+
收藏 11

Because anyone can post a comment to our blog engine, we should protect it a little to avoid automated spam. A simple way to protect a form from this is to add a captcha image.

任何人都可以在博客中发表评论,为了防止一些机器人程序狂发评论,一个简单的办法就是使用验证码图片。

生成验证码图片

We’ll start to see how we can easily generate a captcha image using Play. Basically we will just use another action, except that it will return a binary stream instead of the HTML responses like we’ve returned so far.

接下来我们将演示如何利用Play框架简单的创建验证码图片,只需要使用一个独立的action,并返回验证码图片数据流即可。

Since Play is a full-stack web framework, we try to include built-in constructs for web applications’ most typical needs; generating a captcha is one of them. We can use the play.libs.Images utility to simply generate a captcha image, and then write it to the HTTP response.

因为 Play 是一个全面的Web框架,可以满足web开发中的大多数需求,包括生成验证码图片。我们可以使用 play.libs.Images 工具来生成验证码图片,并将之写到HTTP回应中。

As usual, we will start with a simple implementation. Add the captcha action to the Application controller:

跟一般的action一样,我们在 Application 控制器中添加一个 captcha 的 action 方法如下:

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

Note that we can pass the captcha object directly to the renderBinary() method because the Images.Captcha class implements java.io.InputStream.

需要注意的是,我们将 captcha 对象直接通过 renderBinary 方法来输出,因为 Captcha 类本身是实现了 java.io.InputStream 接口的。

Don’t forget to import play.libs.*.

别忘了引入 play.libs.* 这个包。

Now add a new route to the /yabe/conf/routes file:

另外需要在 routes 中添加 action 的配置:

GET     /captcha                                Application.captcha

And try the captcha action by opening http://localhost:9000/captcha.

在浏览器地址栏中输入 http://localhost:9000/captcha ,你就可以看到如下效果的验证码:

It should generate a random text for each refresh.

而且每次刷新的时候都会显示不同的图片。

How do we manage the state?

如何来管理验证码的状态呢?

Until now it was easy, but the most complicated part is coming. To validate the captcha we need to save the random text written to the captcha image somewhere and then check it at the form submission time.

输出验证码图片是很容易的,但复杂的部分来了。为了对验证码进行检验,我们需要将验证码中的文本保存起来,然后在提交的时候进行检查是否一致。

Of course we could just put the text to the user session at the image generation time and then retrieve it later. But this solution has two drawbacks:

当然我们可以将文本保存在用户会话中,但这个方法有两个缺点:

First, the Play session is stored as a cookie. It solves a lot of problems in terms of architecture but has a lot of implications. Data written to the session cookie are signed (so the user can’t modify them) but not encrypted. If we write the captcha code to the session anybody could easily resolve it by reading the session cookie.

首先,用户会话是作为cookie存储的,这些数据很容易被别有心计的用户利用并破解,使之失去了验证码说应有的效果。

Second, remember that Play is a stateless framework. We want to manage things in a purely stateless way. Typically, what happens if a user simultaneously opens two different blog pages with two different captcha images? We have to track the captcha code for each form.

其次,因为 Play 框架是无状态的,所有的对象都是无状态的,当用户同时打开两个不同的博客页面时,会有不同的验证码,那么怎么来跟踪每个验证码呢?

So to resolve the problem we need two things. We will store the captcha secret key on the server side. Because it is transient data we can easily use the Play Cache. Moreover because cached data have a limited life time it will add one more security mechanism (let’s say that a captcha code will be available for only 10mn). Then to resolve the code later we need to generate a unique ID. This unique ID will be added to each form as an hidden field and implicitely references a generated captcha code.

因此,要解决这个问题需要两步。首先将验证码的加密密钥保存在服务端,我们可以使用Play的缓存来保存这个密钥。此外,由于缓存会在一定时间后生效,因此我们需要添加更安全的机制(例如验证码只在10分钟内有效)。接下来我们需要生成一个唯一的ID,该ID将附加在每个表单中的一个隐藏字段中,用来对应服务器端存储的验证码密钥。

This way we elegantly solve our state problem.

下面是实际的处理代码。

Modify the captcha action as is:

修改 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);
}

Note that the getText() method takes any color as parameter. It will use this color to draw the text.

注意 getText 方法是验证码颜色的参数。

Don’t forget to import play.cache.*.

别忘了引入 play.cache.* 这个包。

Adding the captcha image to the comment form

接下来将验证码图片添加到评论表单中

Now, before displaying a comment form we will generate a unique ID. Then we will modify the HTML form to integrate a captcha image using this ID, and add the ID to another hidden field.

现在,在显示评论表单前,我们会生成一个唯一的ID,然后通过这个ID来生成验证码,并添加一个隐藏字段。

Let’s rewrite the Application.show action:

重写 Application 的 show 方法如下:

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

And now the form in the /yable/app/views/Application/show.html template:

修改模板文件如下:

...
<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>
...

Good start. The comment form now has a captcha image.

打开博客显示页面,就可以看到添加了验证码图片的表单了,如下所示:

Validating the captcha

检查验证码

Now we just have to validate the captcha. We have added the randomID as an hidden field right? So we can retrieve it in the postComment action, then retrieve the actual code from Cache and finally compare it to the submitted code.

接下来就需要在提交评论时候检查验证码是否有效,我们可以通过 postComment 方法来获取到 随机的ID,并从缓存中读取对应的验证码密钥。

Not that difficult. Let’s modify the postComment action.

下面是 postComment 方法的代码:

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);
}

Because we now have more error messages, modify the way we display errors in the show.html template (yes we will just display the first error, it’s good enough):

因为现在有了更多的错误信息,因此我们需要在 show.html 模板中显示出来。

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

Typically for more complex forms, error messages are not managed this way but externalized in the messages file and each error is printed inside the corresponding field.

一般更复杂的表单中,错误信息通常保存在一个名为 messages 的文件。

Check that the captcha is now fully functional.

检查验证码工作是否正常。

Great!

棒极了!

以下是话题补充:

@kelvinliuqf:play 2.0中怎么实现呢 (2013/05/02 11:45)
加载中
返回顶部
顶部