RkBlog

Hardware, programming and astronomy tutorials and reviews.

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 鈥瀢y艣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, Piotr Mali艅ski

Comment article