В мире, полном вредоносных ботов , что вы можете сделать, чтобы защитить свое ценное веб-приложение? Одна из основных вещей, которую вы действительно должны сделать, это добавить в нее возможности CAPTCHA . Если вы не знакомы с (довольно странно звучащим) термином, CAPTCHA — это упрощенный способ убедиться, что пользователь на самом деле является реальным человеком, а не компьютером. Это можно сделать, бросив вызов пользователю и попросив его дать ответ на «проблему». Поскольку компьютеры не могут решить CAPTCHA, любой пользователь, вводящий правильное решение, считается человеком. Наиболее распространенный способ — попросить пользователя ввести буквы или цифры с искаженного изображения, которое появляется на экране.
Скорее всего, вы видели один из файлов CAPTCHA при регистрации на сайте. Ниже приведен пример CAPTCHA из Википедии.
Добавьте SimpleCaptcha в свое приложение
В этом уроке я включу функциональность CAPTCHA в веб-приложение. Я собираюсь использовать платформу SimpeCaptcha , которая используется для генерации пар изображения / ответа CAPTCHA для Java. Сайт предоставляет руководство по установке, но это относится к простым старым приложениям на основе JSP. Я собираюсь показать вам, как интегрировать фреймворк в ваш модный проект GWT.
(Я предполагаю, что у вас уже есть и работает GWT в вашей системе вместе с плагином Google для Eclipse )
Сначала давайте создадим наш проект Eclipse. Выберите файл ? Проект веб-приложения »и предоставьте необходимую информацию, как показано на следующем рисунке. Название проекта будет «CaptchaGwtProject». Убедитесь, что поддержка GWT включена, а Google App Engine — нет.
Прежде чем мы продолжим, важное уведомление здесь. SimpleCaptcha интенсивно использует классы AWT для выполнения рендеринга изображений. Однако, как вы, возможно, знаете, App Engine поддерживает не все классы JRE, и, в частности, включены только некоторые из пакета AWT. Вы можете проверить Белый список класса AppEngine JRE для более подробной информации. Таким образом, невозможно внедрить платформу в приложение, которое будет развернуто в App Engine, и его следует использовать только с платформами, работающими на стандартных JRE.
Следующим шагом является загрузка библиотеки из SourceForge (я использовал версию 1.1.1 ). Добавьте загруженный файл JAR в classpath вашего проекта. Кроме того, не забудьте скопировать файл JAR в папку «CaptchaGwtProject \ war \ WEB-INF \ lib», так как это потребуется во время выполнения встроенного контейнера (Jetty). Страницы JavaDoc проекта можно найти здесь .
Настройка клиентской части
Eclipse автоматически создаст скелет приложения, а также создаст несколько примеров файлов. Найдите класс CaptchaGwtProject, который является точкой входа приложения. Удалите существующее содержимое и замените его следующим:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
|
package com.javacodegeeks.captcha.client; import com.google.gwt.core.client.EntryPoint; import com.google.gwt.core.client.GWT; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.dom.client.KeyCodes; import com.google.gwt.event.dom.client.KeyUpEvent; import com.google.gwt.event.dom.client.KeyUpHandler; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.Image; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.TextBox; public class CaptchaGwtProject implements EntryPoint { private final SignupServiceAsync signupService = GWT.create(SignupService. class ); private final Button sendButton = new Button( "Sign Up" ); public void onModuleLoad() { final TextBox usernameField = new TextBox(); usernameField.setText( "Username here" ); final TextBox passwordField = new TextBox(); passwordField.setText( "Password here" ); final TextBox captchaField = new TextBox(); captchaField.setText( "CAPTCHA Word here" ); final Label responseLabel = new Label(); final Image captchaImage = new Image( "/SimpleCaptcha.jpg" ); usernameField.setFocus( true ); sendButton.addStyleName( "sendButton" ); RootPanel.get( "usernameFieldContainer" ).add(usernameField); RootPanel.get( "passwordFieldContainer" ).add(passwordField); RootPanel.get( "captchaFieldContainer" ).add(captchaField); RootPanel.get( "sendButtonContainer" ).add(sendButton); RootPanel.get( "captchaImageContainer" ).add(captchaImage); RootPanel.get( "responseLabelContainer" ).add(responseLabel); class MyHandler implements ClickHandler, KeyUpHandler { public void onClick(ClickEvent event) { sendDataToServer(); } public void onKeyUp(KeyUpEvent event) { if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER) { sendDataToServer(); } } private void sendDataToServer() { String username = usernameField.getText(); String password = passwordField.getText(); String captcha = captchaField.getText(); sendButton.setEnabled( false ); signupService.performSignup(username, password, captcha, signupCallback); } } MyHandler handler = new MyHandler(); sendButton.addClickHandler(handler); usernameField.addKeyUpHandler(handler); } private AsyncCallback signupCallback = new AsyncCallback() { @Override public void onSuccess(Boolean result) { if (result) { Window.alert( "CAPTCHA was valid" ); } else { Window.alert( "CAPTCHA was invalid" ); } sendButton.setEnabled( true ); } @Override public void onFailure(Throwable caught) { Window.alert( "Error occurred while communicating with server" ); sendButton.setEnabled( true ); } }; } |
Код прост и основан на примере класса, который создается автоматически. Мы добавляем два текстовых поля для ввода пользователя и метку для ответа сервера. Мы также добавляем экземпляр Image , который будет заполнителем для нашей CAPTCHA. Мы устанавливаем его URL в «/SimpleCaptcha.jpg», который будет обрабатываться платформой. Наконец, кнопка используется для вызова вызова на сервер. AsyncCallback использует логическое значение, чтобы указать успешное завершение проверки CAPTCHA на сервере. Обратите внимание, что некоторые важные части веб-приложения (например, проверка пользовательского ввода) намеренно отсутствуют, поэтому мы можем сосредоточиться на части CAPTCHA.
Затем найдите файл HTML с именем «CaptchaGwtProject.html» внутри файла «войны» проекта. Отредактируйте файл и добавьте несколько контейнеров для наших объектов GWT. Код следующий:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
|
<! doctype html> <!-- The DOCTYPE declaration above will set the --> <!-- browser's rendering engine into --> <!-- "Standards Mode". Replacing this declaration --> <!-- with a "Quirks Mode" doctype may lead to some --> <!-- differences in layout. --> < html > < head > < meta http-equiv = "content-type" content = "text/html; charset=UTF-8" > <!-- --> <!-- Consider inlining CSS to reduce the number of requested files --> <!-- --> < link type = "text/css" rel = "stylesheet" href = "CaptchaGwtProject.css" > <!-- --> <!-- Any title is fine --> <!-- --> < title >Web Application Starter Project</ title > <!-- --> <!-- This script loads your compiled module. --> <!-- If you add any GWT meta tags, they must --> <!-- be added before this line. --> <!-- --> < script type = "text/javascript" language = "javascript" src = "captchagwtproject/captchagwtproject.nocache.js" ></ script > </ head > <!-- --> <!-- The body can have arbitrary html, or --> <!-- you can leave the body empty if you want --> <!-- to create a completely dynamic UI. --> <!-- --> < body > <!-- OPTIONAL: include this if you want history support --> < iframe src = "javascript:''" id = "__gwt_historyFrame" tabIndex = '-1' style = "position:absolute;width:0;height:0;border:0" ></ iframe > <!-- RECOMMENDED if your web app will not function without JavaScript enabled --> < noscript > < div style="width: 22em; position: absolute; left: 50%; margin-left: -11em; color: red; background-color: white; border: 1px solid red; padding: 4px; font-family: sans-serif"> Your web browser must have JavaScript enabled in order for this application to display correctly. </ div > </ noscript > < h1 >CAPTCHA Secured Web Application</ h1 > < table align = "center" > < tr > < td colspan = "2" style = "font-weight:bold;" >Please enter your username:</ td > < td id = "usernameFieldContainer" ></ td > </ tr > < tr > < td colspan = "2" style = "font-weight:bold;" >Please enter your password:</ td > < td id = "passwordFieldContainer" ></ td > </ tr > < tr > < td colspan = "2" style = "font-weight:bold;" >Please enter the word:</ td > < td id = "captchaFieldContainer" ></ td > </ tr > < tr > < td id = "sendButtonContainer" ></ td > </ tr > < tr > < td id = "captchaImageContainer" ></ td > </ tr > < tr > < td colspan = "2" style = "color:red;" id = "responseLabelContainer" ></ td > </ tr > </ table > </ body > </ html > |
Обратите внимание, что единственные изменения в автоматически сгенерированном файле — после тегов <h1>.
Наш асинхронный сервис GWT будет очень простым и будет выполнять только одну функцию. Два соответствующих интерфейса показаны ниже:
01
02
03
04
05
06
07
08
09
10
11
|
package com.javacodegeeks.captcha.client; import com.google.gwt.user.client.rpc.RemoteService; import com.google.gwt.user.client.rpc.RemoteServiceRelativePath; @RemoteServiceRelativePath ( "signup" ) public interface SignupService extends RemoteService { boolean performSignup(String username, String password, String userCaptcha); } |
01
02
03
04
05
06
07
08
09
10
|
package com.javacodegeeks.captcha.client; import com.google.gwt.user.client.rpc.AsyncCallback; public interface SignupServiceAsync { void performSignup(String username, String password, String userCaptcha, AsyncCallback callback); } |
(Обратите внимание, что автоматически сгенерированные классы «reetingService »были удалены)
Подготовка серверной части
На стороне сервера основным объектом, который мы используем из библиотеки, является Captcha . Чтобы получить значение Captcha (и сравнить его с вводом пользователя), мы должны получить ссылку на объект HttpSession, связанный с конкретным сеансом. HttpSession может быть получен соответствующим объектом HttpServletRequest . Это стандартный материал Java EE. Не забывайте, что службы GWT на стороне сервера наследуются от RemoteServiceServlet , который наследуется от HttpServletRequest. Базовый запрос может быть получен путем вызова метода getThreadLocalRequest . Обратите внимание, что, как упоминает API, он хранится локально в потоке, поэтому одновременные вызовы могут иметь разные объекты запроса.
Конкретная реализация на стороне сервера заключается в следующем:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
package com.javacodegeeks.captcha.server; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import nl.captcha.Captcha; import com.google.gwt.user.server.rpc.RemoteServiceServlet; import com.javacodegeeks.captcha.client.SignupService; @SuppressWarnings ( "serial" ) public class SignupServiceImpl extends RemoteServiceServlet implements SignupService { public boolean performSignup(String username, String password, String userCaptcha) { HttpServletRequest request = getThreadLocalRequest(); HttpSession session = request.getSession(); Captcha captcha = (Captcha) session.getAttribute(Captcha.NAME); return captcha.isCorrect(userCaptcha); } } |
Расширение SimpleCaptcha
Последний шаг — настройка сервлета, который будет генерировать изображение, показанное пользователю. SimpleCaptcha можно легко расширить, создав класс, который наследуется от предоставленного класса SimpeCaptchaServlet. Соответствующий код следующий:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
package com.javacodegeeks.captcha.server.servlet; import static nl.captcha.Captcha.NAME; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import nl.captcha.Captcha; import nl.captcha.backgrounds.GradiatedBackgroundProducer; import nl.captcha.servlet.CaptchaServletUtil; import nl.captcha.servlet.SimpleCaptchaServlet; public class ExtendedCaptchaServlet extends SimpleCaptchaServlet { private static final long serialVersionUID = 6560171562324177699L; @Override public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { HttpSession session = req.getSession(); Captcha captcha = new Captcha.Builder(_width, _height) .addText() .addBackground( new GradiatedBackgroundProducer()) .gimp() .addNoise() .addBorder() .build(); session.setAttribute(NAME, captcha); CaptchaServletUtil.writeImage(resp, captcha.getImage()); } } |
Переменные «_width» и «_height» передаются в качестве параметров инициализации и считываются из родительского класса. Для создания нового объекта Captcha мы используем класс Captcha.Builder (который опирается на шаблон компоновщика). Затем мы передаем объект в конкретный сеанс и передаем ассоциированный BufferedImage в ответ сервлета.
Обратите внимание, что наша реализация генерирует новое изображение каждый раз, когда пользователь выполняет запрос страницы. Это отличается от реализации SimpleCaptcha по умолчанию, которая использует одно и то же изображение для данного сеанса.
Настройка веб-приложения
Все компоненты связаны через дескриптор web.xml веб-приложения, который в нашем случае выглядит следующим образом:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
<? xml version = "1.0" encoding = "UTF-8" ?> <! DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" < web-app > <!-- Default page to serve --> < welcome-file-list > < welcome-file >CaptchaGwtProject.html</ welcome-file > </ welcome-file-list > <!-- Servlets --> < servlet > < servlet-name >signupServlet</ servlet-name > < servlet-class > com.javacodegeeks.captcha.server.SignupServiceImpl </ servlet-class > </ servlet > < servlet-mapping > < servlet-name >signupServlet</ servlet-name > < url-pattern >/captchagwtproject/signup</ url-pattern > </ servlet-mapping > < servlet > < servlet-name >SimpleCaptcha</ servlet-name > < servlet-class > com.javacodegeeks.captcha.server.servlet.ExtendedCaptchaServlet </ servlet-class > < init-param > < param-name >width</ param-name > < param-value >200</ param-value > </ init-param > < init-param > < param-name >height</ param-name > < param-value >50</ param-value > </ init-param > </ servlet > < servlet-mapping > < servlet-name >SimpleCaptcha</ servlet-name > < url-pattern >/SimpleCaptcha.jpg</ url-pattern > </ servlet-mapping > </ web-app > |
Мы объявляем сервис GWT (класс «RegistrationServiceImpl») и файл приветствия. Здесь нет ничего особенного. Наконец, мы объявляем сервлет, который позаботится о генерации изображения и обработает запрос по URL-адресу «/SimpleCaptcha.jpg» (помните, что он используется в точке входа GWT). Мы также предоставляем параметры инициализации для нашего сервлета (ширина и высота).
Это оно! Запустите проект, и вы должны увидеть что-то вроде следующего:
Каждый раз, когда вы обновляете страницу, создается новое изображение CAPTCHA. При нажатии кнопки «Зарегистрироваться» содержимое поля отправляется на сервер, где пользователь предоставляет значение CAPTCHA, которое сравнивается с существующим в текущем сеансе. В случае неудачи мы видим это:
Вот и все. Теперь ваше приложение может быть немного более безопасным.
Вы можете найти полный проект Eclipse здесь .
ОБНОВЛЕНИЕ: Один из наших читателей спросил, возможно ли добавить функцию «перезагрузить изображение». Это действительно возможно, однако нужно быть осторожным при повторной загрузке изображения с сервера. Как указано в следующем обсуждении, браузер не будет повторно загружать изображение (так как оно имеет тот же URL). Хитрость заключается в добавлении фиктивного параметра, который будет меняться при каждом обновлении. Посмотрите на статью здесь:
http://groups.google.gy/group/google-web-toolkit/browse_thread/thread/be9f1da56b5b1c18
Дело в том, что я создал новую версию проекта, которую можно найти здесь (каламбур).
Наслаждайтесь!