注册后发送电子邮件确认django

12

在我的django应用程序中,注册过程后我会发送电子邮件确认。出于安全原因,我需要找出如何验证我在URL中发送的代码,而不必在用户模型中添加新的代码字段。目前,我正在URL中发送一个随机代码和用户名进行验证,但并没有验证代码。

注册视图

def registrar_usuario_view(request):
alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
code = ''.join(random.choice(alphabet) for i in range(16))
print code
if request.method == 'POST':
    f = RegisterForm(request.POST)
    if f.is_valid():
        usuario = f.cleaned_data['usuario']
        email = f.cleaned_data['email']
        clave = f.cleaned_data['clave']
        confirmar_clave = f.cleaned_data['confirmar_clave']
        captcha = f.cleaned_data['captcha']
        u = User.objects.create_user(username = usuario, email = email, password = clave)
        u.is_active = False
        u.save()
        # Mandamos mail de activacion
        to = email
        html_content = """<h3>Bienvenido Sr/a: %s </h3><p>Para confirmar su registro en el sitio Margonari Servicios Inmobiliarios le solicitamos haga click en el siguiente 
        <a href='http://localhost:8000/confirmacion/%s/%s'>enlace de confirmacion</a><br><p><b>Gracias por formar parte de Margonari Servicios Inmobiliarios.</b></p><br>
        <small>Este es un mensaje enviado automaticamente. Por favor no responda a esta direccion de mail.</small>"""%(usuario, code, usuario)
        msg = EmailMultiAlternatives('Administracion Margonari', html_content, 'from@server.com', [to])
        msg.attach_alternative(html_content, 'text/html') #Definimos el contenido como html
        msg.send() #Enviamos el correo

        messages.add_message(request, messages.SUCCESS, """Los datos han sido ingresados correctamente. Le enviamos un correo de confirmacion 
            a la direccion que nos proporciono. Por favor verifique su casilla de correo no deseado. Muchas gracias.""")
        ctx = {'form':f}
        return render_to_response('users/registrar_usuario.html', ctx, context_instance = RequestContext(request))
    else:
        ctx = {'form':f}
        return render_to_response('users/registrar_usuario.html', ctx, context_instance = RequestContext(request))

f = RegisterForm()
ctx = {'form':f}
return render_to_response('users/registrar_usuario.html', ctx, context_instance =    RequestContext(request))

确认页面

def confirmacion_view(request, code, user):
user = User.objects.get(username = user)
user.is_active = True
user.save()
return HttpResponseRedirect('/')

URL

URL指的是统一资源定位符,是Web上用于定位和访问资源的地址。
url(r'^confirmacion/(?P<code>.*)/(?P<user>.*)/$', 'confirmacion_view', name = 'vista_confirmacion'),

为什么不使用某种缓存(例如:memcache,retis等)来存储新用户的注册“代码”,直到它被验证? - Casey Falk
另外,请澄清您的标题以反映问题——而不是您已经完成的内容。 :) - Casey Falk
如果您是新手网站开发者,不想在网站安全方面犯错,请使用 django-registration 包。 - Mikko Ohtamaa
1
不建议使用django-registration,因为它已经没有得到维护。 - Andrés Da Viá
1个回答

17

Django提供了一个令牌创建机制,无需重新发明轮子。 既然我不使用基于函数的视图,并且这里的重点不是重构您的代码(无论如何我都会在CBV中执行它),因此我将输出一个示例,以说明您如何使用它。

from django.contrib.auth.tokens import default_token_generator
from django.utils.http import urlsafe_base64_encode
from django.utils.encoding import force_bytes

new_user = User.objects.create_user(username=usuario,
                                    email=email,
                                    password=clave)
new_user.save()
token = default_token_generator.make_token(new_user)
uid = urlsafe_base64_encode(force_bytes(new_user.pk))

你可以将令牌发送到用户的电子邮件中,令牌的 URL 应该像这样:

url(r'^users/validate/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
    activationview,
    name='user-activation-link')

在你的激活视图中的某个地方:

from django import http

uidb64 = request.GET.get('uidb64')
token = request.GET.get('token')

if uidb64 is not None and token is not None:
    from django.utils.http import urlsafe_base64_decode
    uid = urlsafe_base64_decode(uidb64)
    try:
        from django.contrib.auth import get_user_model
        from django.contrib.auth.tokens import default_token_generator
        user_model = get_user_model()
        user = user_model.objects.get(pk=uid)
        if default_token_generator.check_token(user, token) and user.is_active == 0:
            # Do success stuff...
            return http.HttpResponseRedirect(a_success_url)
    except:
        pass

return http.HttpResponseRedirect(a_failure_url)

我已经根据您的回复更新了我的代码,这似乎是我需要的,但由于某种原因,如果uidb64不为None且token不为None,则无法通过此验证:一旦我获得像http://localhost:8000/confirmacion/MTQ/3u4-d6a9f8e53771675351a5/这样的URL,这是我的最终代码https://dpaste.de/J7Fm#L41。 - Andrés Da Viá
1
@AndrésDaViá,你是如何解决这个问题的?我也遇到了同样的问题。第一次发送电子邮件并单击验证链接时,由于令牌不匹配而引发错误。第二次发送验证链接并单击链接可以正常工作。 错误是由令牌生成器代码中的以下片段引起的:if not constant_time_compare(self._make_token_with_timestamp(user, ts), token): return False谢谢。 - Lal
@Lal 我发现使用Django auth的default_token_generator存在同样的问题。许多答案覆盖了_make_hash_value()以使用pk、时间戳和is_active。我认为这个方法更好,因为默认值使用login_timestamp,而未创建用户没有此属性。 - Addison Klinke

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接