Grafiki Captcha w Django

Captcha to grafika z tekstem, który trzeba wpisać do pola formularza by można go było poprawnie wysłać. Celem takich zabiegów jest walka z botami spamującymi czy np. rejestrującymi tysiące kont użytkowników w celu dalszego spamowania strony. Użytkownicy Django opublikowali kilka rozwiązań, m.in. django-captcha.
Captcha ogranicza dostępność strony dla osób z ograniczeniami, w szczególności dla osób niewidomych. Captcha powinna być stosowana z konieczności i tak by nie ograniczała w znaczący sposób dostępu do zawartości strony.

Jak to działa ?

- Generujemy losowy łańcuch i umieszczamy na grafice
- Pokazujemy grafikę w formularzu
- Tworzymy hasz md5 czy sha1, który umieszczamy w ukrytym polu formularza lub używamy sesji czy cookie
- Użytkownik wpisuje tekst i wysyła formularz, tworzymy hasz tego tekstu i porównujemy z oryginalnym haszem...

Prosta Captcha z PIL

Wymagania

- PIL (Python imaging library)
- Prosta grafika (mały banner o nieagresywnym tle)
- Plik TTF czcionki - czytelnej ale nie za prostej

Kod

Użyłem małego pliku graficznego bg.jpg, który umieściłem w katalogu /site_media:
Oprócz tego użyłem czcionki SHERWOOD.TTF, plik umieściłem w tym samym katalogu. Następnie skonfigurowałem prosty projekt/aplikację django zwracający widok "captcha" pod głównym URLem: /

Kod views.py:
from django.shortcuts import render_to_response

def captcha(request):
		# generowanie losowych elementów
		from random import choice
		# elementy PIL, sha do hasza
		import Image, ImageDraw, ImageFont, sha
		# tworzymy losowy łańcuch 5 znaków, zauważ brak dużego i
		SALT = settings.SECRET_KEY[:20]
		imgtext = ''.join([choice('QWERTYUOPASDFGHJKLZXCVBNM') for i in range(5)])
		#tworzymy hasz
		imghash = sha.new(SALT+imgtext).hexdigest()
		# tworzymy grafikę z naniesionym tekstem
		# PIL "code" – otwórz grafikę, nanieś tekst
		im=Image.open('media/bg.jpg')
		draw=ImageDraw.Draw(im)
		font=ImageFont.truetype('media/SHERWOOD.TTF', 18)
		draw.text((10,10),imgtext, font=font, fill=(100,100,50))
		# zapisujemy grafikę
		im.save('media/bg2.jpg',"JPEG")
		
		if request.POST:
			data = request.POST.copy()
			# czy hasze się zgadzają ?
			if data['imghash'] == sha.new(SALT+data['imgtext']).hexdigest():
				# captcha ok
				# zapis danych czy coś
				return render_to_response('form.html', {'ok': True, 'hash': imghash})
			else:
				# captcha zła
				# zwróć formularz
				return render_to_response('form.html', {'error': True, 'hash': imghash})
		# no post data, show the form
		else:
			return render_to_response('form.html', {'hash': imghash})

Kod szablonu form.html:
{% if error %}
		<center><b>FAILURE</b></center>
	{% endif %}
	{% if ok %}
		<center><b>SUCCESS ! :)</b></center>
	{% endif %}
	<form action="." method="POST"><input type="hidden" value="{{ hash }}" name="imghash">
	<b>Text from the image</b><br />(Use CAPITAL letters)<br />
	<input type="text" size="20" name="imgtext"><br /><img src="/site_media/bg2.jpg"><br />
	<br /><input type="submit" value="Send Form" class="actiontable">
	</form>

Wynik


Własny Manipulator

Dla starego systemu formularzy (Django <= 0.95) możemy tworzyć własne manipulatory. Dodatkowo powyższy kod ma jedną wadę – jeżeli będzie dużo wywołań widoku z captcha może się zdarzyć że danych użytkownik nie zobaczy poprawnej grafiki (przeznaczonej dla niego), jako że każde wykonanie widoku zapisuje grafikę do tego samego pliku. Poniższy kod używa manipulatora oraz rozwiązuje problem „wyścigu” zapisu pliku captchy:
from os import remove
class LoginForm(forms.Manipulator):
	def __init__(self):
		self.fields = (forms.TextField(field_name="login", length=30, maxlength=200, is_required=True),
		forms.PasswordField(field_name="password", length=30, maxlength=200, is_required=True),
		forms.TextField(field_name="imgtext", is_required=True, validator_list=[self.hashcheck]),
		forms.TextField(field_name="imghash", is_required=True),)
	def hashcheck(self, field_data, all_data):
		import sha
		SALT = settings.SECRET_KEY[:20]
		if not all_data['imghash'] == sha.new(SALT+field_data).hexdigest():
			raise validators.ValidationError("Captcha Error.")

def loginlogout(request):
	from django.contrib.auth import authenticate, login
	if not request.user.is_authenticated():
		temp = settings.SITE_IMAGES_DIR_PATH + request.META['REMOTE_ADDR'] + '.jpg'
		tempname = request.META['REMOTE_ADDR'] + '.jpg'
		# captcha image creation
		from random import choice
		import Image, ImageDraw, ImageFont, sha
		# create a 5 char random strin and sha hash it
		SALT = settings.SECRET_KEY[:20]
		imgtext = ''.join([choice('QWERTYUOPASDFGHJKLZXCVBNM') for i in range(5)])
		imghash = sha.new(SALT+imgtext).hexdigest()
		# create an image with the string
		im=Image.open(settings.SITE_IMAGES_DIR_PATH + '../bg.jpg')
		draw=ImageDraw.Draw(im)
		font=ImageFont.truetype(settings.SITE_IMAGES_DIR_PATH + '../SHERWOOD.TTF', 24)
		draw.text((10,10),imgtext, font=font, fill=(100,100,50))
		im.save(temp,"JPEG")
		
		manipulator = LoginForm()
		# log in user
		if request.POST:
			data = request.POST.copy()
			errors = manipulator.get_validation_errors(data)
			if not errors:
				manipulator.do_html2python(data)
				user = authenticate(username=data['login'], password=data['password'])
				if user is not None:
					login(request, user)
					remove(temp)
					return HttpResponseRedirect("/user/")
				else:
					data['imgtext'] = ''
					form = forms.FormWrapper(manipulator, data, errors)
					return render_to_response('userpanel/' + settings.ENGINE + '/login.html', {'loginform': True, 'error': True, 'hash': imghash, 'form': form, 'theme': settings.THEME, 'engine': settings.ENGINE, 'temp':tempname})
		# no post data, show the login forum
		else:
			errors = data = {}
		form = forms.FormWrapper(manipulator, data, errors)
		return render_to_response('userpanel/' + settings.ENGINE + '/login.html', {'loginform': True, 'hash': imghash, 'form': form, 'theme': settings.THEME, 'engine': settings.ENGINE, 'temp':tempname})
	else:
		# user authenticated
		if request.GET:
			# logout user
			data = request.GET.copy()
			if data['log'] == 'out':
				from django.contrib.auth import logout
				logout(request)
				return HttpResponseRedirect("/user/")
		return HttpResponseRedirect("/user/")
W tym przypadku plik graficzny zapisywany jest w pliku o nazwie bazującej na IP użytkownika.

Rozpoznawanie tekstu z grafik przez roboty

Captcha nie jest rozwiązaniem w pełni skutecznym. Istnieją już rozwiązania zdolne odczytać tekst z grafik. By utrudnić ich odczytanie grafiki z captcha zawierają falisty tekst czy różne linie zamazujące obraz co i ludziom utrudnia odczytanie tekstu. Zamiast jeszcze bardziej zamazywać obrazek można skorzystać z ozdobnych, lecz czytelnych czcionek:
RkBlog

Django, 14 July 2008

Comment article
Comment article RkBlog main page Search RSS Contact