갈루아의 반서재



장고 사이트에 reCAPTCHA 넣기

Add reCAPTCHA to Django site




각종 봇과 스팸으로부터 사이트를 보호하기 위해 구글의 reCATCHA 를 많이 사용하는데요. 본 포스팅에서는 장고로 만들어진 사이트에 이를 손쉽게 적용해보는 방법을 살펴겠습니다.







1. Requirements


1) reCAPTCHA > Get reCAPTCHA 클릭하여 다음과 같이 키를 획득한다.




사이트 등록이 끝나면 2개의 키를 받게 되는데 Site keySecret key가 바로 그것이다. 여기서 Site key(사이트와 사용자간)는 원하는 페이지에 렌더링하기 위해 사용되고, Secret key(사이트와 구글간 통신)는 settings.py 모듈에 등록하여 구글과 사이트 간의 통신을 위해 사용된다. 




2) settings.py 파일에 아래와 같이 reCAPTCHA 키를 넣는다.


settings.py

1
2
RECAPTCHA_PUBLIC_KEY = 'MyRecaptchaKey123'
RECAPTCHA_PRIVATE_KEY = 'MyRecaptchaPrivateKey456'
cs





2. Implementing the reCAPTCHA


reCAPTCHA 위젯을 구현하는 가장 손쉬운 방법은 아래와 같이 자바스크립트와 g-recaptcha 태그를 필요한 부분에 넣는 것이다. g-recaptcha 태그는 class name이 'g-recaptcha' 인 DIV 요소이며, data-sitekey 는 앞서 발급받은 Site Key이다. 



1
2
3
4
5
6
7
8
9
10
11
12
13
<html>
  <head>
    <title>reCAPTCHA demo: Simple page</title>
     <script src="https://www.google.com/recaptcha/api.js" async defer></script>
  </head>
  <body>
    <form action="?" method="POST">
      <div class="g-recaptcha" data-sitekey="your_site_key"></div>
      <br/>
      <input type="submit" value="Submit">
    </form>
  </body>
</html>
cs



1
2
3
<script src='https://www.google.com/recaptcha/api.js'></script>
<div class="g-recaptcha" data-sitekey="******************************************">
</div>
cs



구현된 모습은 아래와 같다. 








3. Validating the reCAPTCHA


마지막 남은 작업은 https://www.google.com/recaptcha/api/siteverify 로 POST 요청을 보내 데이터가 유효한지 검증하는 것이다. 


views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import json
import urllib
 
from django.shortcuts import render, redirect
from django.conf import settings
from django.contrib import messages
 
from .models import Questions
from .forms import QuestionCreationForm
 
def questions_ask(request):
    if request.method == 'POST':
        form = QuestionCreationForm(request.POST)
        if form.is_valid():
            
            recaptcha_response = request.POST.get('g-recaptcha-response')
            url = 'https://www.google.com/recaptcha/api/siteverify'
            values = {
                'secret': settings.RECAPTCHA_PRIVATE_KEY,
                'response': recaptcha_response
            }
            data = urllib.parse.urlencode(values).encode()
            req =  urllib.request.Request(url, data=data)
            response = urllib.request.urlopen(req)
            result = json.loads(response.read().decode())
            
            if result['success']:
                form.save()
                messages.success(request, 'New question added with success!')
                return HttpResponseRedirect("/questions"
            else:
                messages.error(request, 'Invalid reCAPTCHA. Please try again.')
                return HttpResponseRedirect("/questions/ask"
    else:
        form = QuestionCreationForm()
    return render(request, "/questions_ask.html", {
        'form': form,
    })
cs


questions_ask.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div class="row">
    <div class="col-sm-11 offset-sm-1">
        <div class="form-group">
            <textarea class="form-control" name="body" rows="12" placeholder="Click here to ask"></textarea>
        </div>
        <script src='https://www.google.com/recaptcha/api.js'></script>
        <div class="g-recaptcha" data-sitekey="*****************************************">
        </div>
        <div><p>
        </div>
        <div class="form-group">
            <button type="submit" class="btn btn-success">Save changes</button>
        </div>
    </div>
</div>
 
cs




g-recaptcha tag attributes and grecaptcha.render parameters

g-recaptcha tag attributegrecaptcha.render parameterValueDefaultDescription
data-sitekeysitekeyYour sitekey.
data-themethemedark lightlightOptional. The color theme of the widget.
data-typetypeaudio imageimageOptional. The type of CAPTCHA to serve.
data-sizesizecompact normalnormalOptional. The size of the widget.
data-tabindextabindex0Optional. The tabindex of the widget and challenge. If other elements in your page use tabindex, it should be set to make user navigation easier.
data-callbackcallbackOptional. The name of your callback function to be executed when the user submits a successful CAPTCHA response. The user's response, g-recaptcha-response, will be the input for your callback function.
data-expired-callbackexpired-callbackOptional. The name of your callback function to be executed when the recaptcha response expires and the user needs to solve a new CAPTCH

https://developers.google.com/recaptcha/docs/display



data-theme 값을 통해 테마의 색상 선택이 가능하다


data-theme="dark"

  


data-theme="light" (default value)






4. reCAPTCHA Decorator


마지막으로 reCAPTCHA를 데코레이터로 등록하여 사용하는 방법이다. 


decorators.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from functools import wraps
 
from django.conf import settings
from django.contrib import messages
 
import requests
 
def check_recaptcha(view_func):
    @wraps(view_func)
    def _wrapped_view(request, *args, **kwargs):
        request.recaptcha_is_valid = None
        if request.method == 'POST':
            recaptcha_response = request.POST.get('g-recaptcha-response')
            data = {
                'secret': settings.RECAPTCHA_PRIVATE_KEY,
                'response': recaptcha_response
            }
            r = requests.post('https://www.google.com/recaptcha/api/siteverify', data=data)
            result = r.json()
            if result['success']:
                request.recaptcha_is_valid = True
            else:
                request.recaptcha_is_valid = False
                messages.error(request, 'Invalid reCAPTCHA. Please try again.')
        return view_func(request, *args, **kwargs)
    return _wrapped_view
cs



views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from django.shortcuts import render, redirect
from django.conf import settings
from django.contrib import messages
 
from .decorators import check_recaptcha
from .models import Answers
from .forms import AnswerCreationForm
 
@check_recaptcha
def answers_new(request):
    if request.method == 'POST':
        form = AnswerCreationForm(request.POST)
        if form.is_valid() and request.recaptcha_is_valid:
            answer_obj.save()
            messages.success(request, 'New Answer added with success!')
            return redirect('app.views.questions_view', question.pk)
        else:
            return redirect('app.views.questions_view', question.pk)
    else:
        form = QuestionCreationForm()
    return render(request, "app/questions_view.html", {
        'form': form,
    })
cs


questions_view.html

1
2
3
4
5
6
7
8
{% if messages %}
    {% for message in messages %}
    <font color="red">{{ message }}</font>
    {% endfor %}
{% endif %}        
        
<script src='https://www.google.com/recaptcha/api.js'></script>
<div class="g-recaptcha" data-sitekey="*******************" data-theme="light"></div>
cs