cherry.png

Создаем простую капчу

07.11.2017
Небольшой пример кода Java для создания картинки с капчей и пример добавления его к коду сайта или блога.
Так выглядит рабочий вариант нашей капчи:
captcha
reload.png
Сначала определимся с алгоритмом. На вход будем подавать случайную комбинацию букв, затем каждую букву будем поворачивать на 35 градусов по часовой или против часовой стрелки поочередно, и в конце соберем полученные изображения в одну строчку.
Почему именно 35 градусов? Если взять угол больше, тогда самому будет сложно разгадать такую капчу. Например, буквыNиZбудут похожи друг на друга. Если взять угол меньше 35 градусов, то такую капчу будет легко разгадать с помощьюOCRпрограмм.
Текстовую строку со значением капчи будем хранить в атибуте сессии. Для тех, кто не знаком сJavaскажу, что атрибуты сессии, как и сама сессия, вJavaхранятся на сервере приложений и недоступны браузеру пользователя. Так что никакие скрытыеIDпод капчей в нашем случае просто не нужны. При отправке формы на сервер мы будем сравнивать сохраненное значение капчи с тем, что ввел пользователь, и далее будем принимать решение - обрабатывать его запрос, или нет.
Итак, переходим к самому алгоритму:
1. Для начала нам понадобится коллекция каких-либо символов, например, заглавных английских букв. Я для своего блога выбрал вот такой вариант:
letter_a.pngletter_b.pngletter_c.pngletter_d.pngletter_e.pngletter_f.pngletter_g.pngletter_h.pngletter_i.pngletter_j.pngletter_k.pngletter_l.pngletter_m.pngletter_n.pngletter_o.pngletter_p.pngletter_q.pngletter_r.pngletter_s.pngletter_t.pngletter_u.pngletter_v.pngletter_w.pngletter_x.pngletter_y.pngletter_z.png
Но для лучшей защиты от спама рекомендуется расширить эту коллекцию, дополнив ее строчными буквами и цифрами. Картинки должны быть в форматеGIFилиPNGс поддержкой прозрачности, т.к. такие картинки затрудняют процесс машинного распознавания текста - не всеOCRпрограммы умеют работать с такими изображениями.
2. Создаем сервлет, к которому мы будем обращаться:

Java

@WebServlet(name = "Captcha", urlPatterns = {"/captcha"})
public class Captcha extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doResponse(request, response);
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doResponse(request, response);
    }

    protected void doResponse(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //TODO
    }
}
Далее будем постепенно заполнять методdoResponseи дополнять его вспомогательными методами.
3. Получаем случайную комбинацию букв:

Java

private static char[] getRandomStringImpl() {

    char[] randomString = new char[8];

    Random random = new Random();

    int capitalLetter;

    for (int i = 0; i < 8; i++) {
        // заглавные английские буквы в таблице
        // символов ASCII начинаются с кода 65
        capitalLetter = 65 + random.nextInt(26);
        randomString[i] = (char) capitalLetter;
    }

    return randomString;
}
4. Вызываем этот метод из методаdoResponseи записываем возвращаемое значение в сессию:

Java

char[] captcha = getRandomStringImpl();
request.getSession().setAttribute("captcha", String.valueOf(captcha));
Таким образом мы получили значение капчи, которое потом будем сравнивать с тем, что пользователь введет в форму, если конечно, он пользователь, а не робот.
5. Теперь собираем массив картинок с буквами и поворачиваем каждую из них на 35 градусов:

Java

BufferedImage[] images = new BufferedImage[8];

for (int i = 0; i < captcha.length; i++) {
    images[i] = ImageIO.read(Captcha.class.getResourceAsStream("/captcha/" + captcha[i] + ".png"));
    if (i % 2 == 0) {
        images[i] = rotateImage(images[i], 35);
    } else {
        images[i] = rotateImage(images[i], -35);
    }
}
6. Создаем метод, поворачивающий картинку на заданный угол:

Java

private BufferedImage rotateImage(BufferedImage buffImage, double angle) {

    double radian = Math.toRadians(angle);
    double sin = Math.abs(Math.sin(radian));
    double cos = Math.abs(Math.cos(radian));

    int width = buffImage.getWidth();
    int height = buffImage.getHeight();

    int nWidth = (int) Math.floor((double) width * cos + (double) height * sin);
    int nHeight = (int) Math.floor((double) height * cos + (double) width * sin);

    BufferedImage rotatedImage = new BufferedImage(nWidth, nHeight, BufferedImage.TYPE_INT_ARGB);

    Graphics2D graphics = rotatedImage.createGraphics();

    graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
    graphics.translate((nWidth - width) / 2, (nHeight - height) / 2);
    graphics.rotate(radian, (double) (width / 2), (double) (height / 2));
    graphics.drawImage(buffImage, 0, 0,null);
    graphics.dispose();

    return rotatedImage;
}
7. Теперь соединяем полученные картинки в одну строку. Чтобы буквы немного наезжали друг на друга, сдвигаем каждую последующую букву влево на 40% ширины предыдущей буквы:

Java

int imageSize = 30;
// вычисляем гипотенузу по теореме Пифагора
// максимальный размер изображения получается при угле поворота 45 градусов
int rotatedImageSize = (int) Math.sqrt(imageSize * imageSize * 2);

BufferedImage captcha_img = new BufferedImage(rotatedImageSize * 7 / 10 * 6 + rotatedImageSize, rotatedImageSize, BufferedImage.TYPE_INT_ARGB);

for (int i = 0; i < captcha.length; i++) {
    captcha_img.getGraphics().drawImage(images[i],rotatedImageSize * i / 10 * 6, 0, null);
}
8. Последний шаг - возвращаем пользователю полученную картинку с капчей:

Java

ImageIO.write(captcha_img, "png", response.getOutputStream());
Все готово, осталось только собрать весь код вединое целое
Ну а рабочий пример этого кода размещен на этом сайте: в комментариях, и на страницеКонтакты

facebookvkontaktetwitterodnoklassnikimailrulivejournal

Комментарии

O0O0O0O0
Комментатор
01.01.1970 03:00 (MSK)
Комментариев пока нет.. Вы можете стать первым..