Grafiki Captcha w Django
14 July 2008
Comments
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:
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/")
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
Comment article