Skip to main content

RichFaces i Captcha

Jeśli w pisanej właśnie przez nas aplikacji J2EE potrzebujemy użyć Captcha'y - czyli prostego zabezpieczenia przed spamrobotami lub złośliwymi automatami, najczęściej sięgamy po bibliotekę JCaptcha lub SimpleCaptcha. Z reguły dość łatwo znaleźć informację i przykłady użycia tych bibliotek z różnymi frameworkami. Obecnie używam RichFaces i w tym akurat przypadku nie udało mi się znaleźć żadnego rozsądnego przykładu. Poniżej opiszę jak udało mi się umieścić (w miarę elegancki sposób jak mi się zdaje) obrazek captchy na jednym z ekranów mojej aplikacji.

Przez chwilę próbowałem JCaptcha'y. Ma świetne możliwości panowania nad generowanym obrazkiem, ale wydała mi się nieco zbyt skomplikowana w użyciu. Może za krótko próbowałem, ale kiedy sięgnąłem po SimpleCaptcha, ta urzekła mnie swoją prostotą ;-).
Głównym założeniem, wynikającym zresztą jasno z wymagań, było to że na tym ekranie musi być użyty Ajax. I fajnie by było gdyby udało się skorzystać jedynie tagów z RichFaces. Na szczęście mamy tag <rich:paint2d>. Wpisujemy więc do kodu naszej strony coś takiego:

<rich:paint2D id="captchaImg"
		width="#{registerFormBackingBean.captchaWidth}"
		height="#{registerFormBackingBean.captchaHeight}"
		format="png" 
		paint="#{registerFormBackingBean.paintCaptcha}"
		data="#{registerFormBackingBean.captcha}"/>

Dodajemy również do naszego formularza pole w które wpisane ma zostać napis z obrazka Captcha.
Teraz, w naszym backing beanie (czy też jeśli ktoś upiera się na tłumaczenie - w naszym ziarenku wsparcia [błeee];) musimy dodać cztery metody:

  1. getCaptchaWidth() - powinna zwracać szerokość generowanego obrazka
  2. getCaptchaHeight() - powinna zwracać wysokość generowanego obrazka
  3. paintCaptcha() - metoda właściwie generująca obrazek
  4. getCaptcha() - metoda ma zwracać wartość wygenerowaną podczas rendorowania obrazka i służącą jako klucz do cache'a

Metoda paintCaptcha() (proszę zwrócić uwagę, że nie getPaintCaptcha()), musi mieć następujący nagłówek:

void paintCaptcha(java.awt.Graphics2D,java.lang.Object));

Pierwszy argument to obiekt typu java.awt.Graphics2D - niejako "na nim" będziemy "rysować" (generować) obrazek. Drugi, to obiekt przekazany jako wynik metody określonej przez atrybut data.
Moja implementacja tych metod mieszcząca się w backingBeanie wygląda następująco:

public void paintCaptcha(Graphics2D g2d, Object obj) {
	Captcha captcha = (Captcha)obj;
	BufferedImage secureImage = captcha.getImage();
	if (secureImage == null) {
		log.warn("Generowanie obrazka CAPTCHA zwróciło NULL");
		return;
	}
	try {
		g2d.setClip(0, 0, secureImage.getWidth(), secureImage.getHeight());
		g2d.drawImage(secureImage, 0, 0, null);
	} catch (Exception ex) {
		log.error("Błąd generowania obrazka CAPTCHA", ex);
	}
}
 
public int getCaptchaWidth() {
	return CAPTCHA_IMAGE_WIDTH;
}
 
public int getCaptchaHeight() {
	return CAPTCHA_IMAGE_HEIGHT;
}
 
public Captcha getCaptcha() {
	Captcha.Builder captchaBuilder = new Captcha.Builder(CAPTCHA_IMAGE_WIDTH, CAPTCHA_IMAGE_HEIGHT);
	captchaBuilder.addText();
	captchaBuilder.addNoise(new CurvedLineNoiseProducer());
	captchaBuilder.addBackground(new GradiatedBackgroundProducer());
	captcha = captchaBuilder.build();
	return captcha;
}

W metodzie getCaptcha() tworzony jest obiekt klasy CaptchaBuilder na którym wykonywane są metody addText() - która generuje losowy napis a następnie umieszcza go na obrazku oraz addNoise() - generującą zakłócenia (można ją wywołać wielokrotnie) oraz addBackground() - przygotowującą tło obrazka. Wynikiem wywołania metody build jest obiekt klasy Captcha. Zapisuję go do zmiennej składowej captcha naszego ziarenka.
Teraz wystarczy już w metodzie obsługującej przycisk wysyłający formularz dokonać sprawdzenia czy tekst wpisany przez użytkownika zgadza się z tym na naszym obrazku captcha. Zakładając, że wpisany w polu formularza napis znajdzie się w zmiennej o nazwie captchaInputText, nasz kod weryfikujący jego poprawność może wyglądać jakoś tak:

if (captcha != null && captcha.isCorrect(captchaInputText)) {
	log.debug("Poprawnie podany CAPTCHA");
	// tutaj obsługujemy dane z naszego formularza
	// ...
 
} else {
	log.debug("Nieoprawnie podany CAPTCHA lub CAPTCHA pusty");
	// tutaj informujemy użytkownika o błędnie wpisanej wartości
	// ....
 
}

Przy weryfikacji korzystamy z metody isCorrect() udostępnianej przez instancję klasy Captcha. To już wszystko. Mamy w naszym formularzy dynamicznie generowany obrazek captcha'y.