Django中的电子邮件验证

15

我正在使用Django开发一个Web应用程序。我尝试使用tokengenerator实现密码重置并创建验证电子邮件,但电子邮件未被激活。

问题如下:

  1. 当用户提供电子邮件时,应检查该电子邮件是否在数据库中存在(数据库将更新用户电子邮件)。
  2. 在验证电子邮件存在于数据库后,用户会被提示创建密码。
  3. 创建密码后,用户可以登录到相应的页面。

有什么解决方案吗? 我已经尝试并遵循了以下方法:

https://medium.com/@frfahim/django-registration-with-confirmation-email-bb5da011e4ef

上述文章帮助我发送了电子邮件,但用户在验证电子邮件后未被激活。 尽管我尝试检查电子邮件验证是否可行,但该帖子不符合我的要求。

是否有Django的第三方模块或任何解决方案可以满足我提到的需求?

4个回答

32

我找到了一个解决方案,但对于第二个要求,用户必须在创建账户时输入密码。主要目标是验证用户提供的电子邮件。

模型

class Yourmodel(models.Model):
    first_name = models.CharField(max_length=200)
    second_name = models.CharField(max_length=200)
    email = models.EmailField(max_length=100)

令牌

from django.contrib.auth.tokens import PasswordResetTokenGenerator
from django.utils import six
class TokenGenerator(PasswordResetTokenGenerator):
    def _make_hash_value(self, user, timestamp):
        return (
            six.text_type(user.pk) + six.text_type(timestamp) +
            six.text_type(user.is_active)
        )
account_activation_token = TokenGenerator()

视图

from django.contrib.auth import get_user_model
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
from django.contrib.sites.shortcuts import get_current_site
from .tokens import account_activation_token
from django.core.mail import send_mail
from django.utils.encoding import force_bytes
from django.template.loader import render_to_string

def signup(request):
    User = get_user_model()
    if request.method == 'POST':
        form = SignupForm(request.POST)
        if form.is_valid():
            email = form.cleaned_data.get('email')
            if Yourmodel.objects.filter(email__iexact=email).count() == 1:
                user = form.save(commit=False)
                user.is_active = False
                user.save()
                current_site = get_current_site(request)
                mail_subject = 'Activate your account.'
                message = render_to_string('email_template.html', {
                            'user': user,
                            'domain': current_site.domain,
                            'uid': urlsafe_base64_encode(force_bytes(user.pk)),
                            'token': account_activation_token.make_token(user),
                        })
                to_email = form.cleaned_data.get('email')
                send_mail(mail_subject, message, 'youremail', [to_email])
                return HttpResponse('Please confirm your email address to complete the registration')
     else:
        form = SignupForm()
    return render(request, 'regform.html', {'form': form})

def activate(request, uidb64, token):
    User = get_user_model()
    try:
        uid = force_text(urlsafe_base64_decode(uidb64))
        user = User.objects.get(pk=uid)
    except(TypeError, ValueError, OverflowError, User.DoesNotExist):
        user = None
    if user is not None and account_activation_token.check_token(user, token):
        user.is_active = True
        user.save()
        return HttpResponse('Thank you for your email confirmation. Now you can login your account.')
    else:
        return HttpResponse('Activation link is invalid!')

表单

from django.contrib.auth.forms import UserCreationForm


class SignupForm(UserCreationForm):
    class Meta:
        model = User
        fields = ('username', 'email', 'password1', 'password2')

电子邮件模板

{% autoescape off %}
Hi ,
Please click on the link to confirm your registration,
http://{{ domain }}{% url 'activate' uidb64=uid token=token %}
{% endautoescape %}

regform.html

{% csrf_token %}
{% for field in form %}
<label >{{ field.label_tag }}</label>
{{ field }}
{% endfor %}

如果您不想在您的模型中与电子邮件地址进行比较,可以跳过此步骤,这将直接发送电子邮件到注册时提供的电子邮件地址,无需进一步验证。
email = form.cleaned_data.get('email')
if Yourmodel.objects.filter(email__iexact=email).count() == 1:

为什么哈希字符串的选择与Django auth内置的default_token_generator不同,它们使用pk、password、login_timestamp、timestamp和email。更改它会使其不安全吗? - Addison Klinke
1
你从哪里获取了force_byte?我却在从Django Utils中导入six时遇到了问题。 - nassim
2
@nassim 在 Stack Overflow 上的问题: ImportError:“无法从 Django 工具中导入名称 six” - Joel Deleep
1
顺便说一下,force_bytes 很可能来自 from django.utils.encoding import force_bytes - Nat Riddle
1
在顶部添加from django.template.loader import render_to_string - Shayan

4

对于第一个答案,您需要在 urls.py 中进行添加。

path('emailVerification/<uidb64>/<token>', views.activate, name='emailActivate')

emailVerification.html必须像这样:

    Hi ,
Please click on the link to confirm your registration,
http://{{ domain }}/emailVerification/{{ uid }}/{{ token }}

2

我有一个关于你第一个问题的答案:

如果您使用基于PasswordResetView + PasswordResetConfirmView的用户密码重置,您可以执行以下操作:

PasswordResetView负责向用户发送电子邮件。它使用自己的表单来输入用户电子邮件-PasswordResetForm。您可以制作自己的表单并从PasswordResetForm继承。 例如:

Original Answer翻译成"最初的回答"


class PRForm(PasswordResetForm):
    def clean_email(self):
        email = self.cleaned_data['email']
        if not User.objects.filter(email__iexact=email, is_active=True).exists():
            msg = "There is no user with this email."
            self.add_error('email', msg)
        return email

# User – your user model or any custom model if you have one instead of the default one

这段代码将不允许控制器向您的数据库中不存在的电子邮件地址发送电子邮件。

然后在您的视图中指定此表单:


class PassResView(RatelimitMixin,  PasswordResetView):
    success_url = 
    from_email = 
    subject_template_name =
    email_template_name =
    success_message = 
    template_name = 
    form_class = PRForm  # here is a custom form
    ratelimit_key = 'ip'
    ratelimit_rate = '10/5m'
    ratelimit_block = True
    ratelimit_method = ('GET', 'POST')

RatelimitMixin不会允许有人通过运行您的数据库来暴力破解它。您可以选择使用它或不使用它-这取决于您。

原始答案:最初的回答


考虑我的电子邮件地址存在于模型中class Test(models.Model):first_name = models.CharField(max_length=200) email_address = models.CharField(max_length=200),我想使用这个电子邮件地址进行检查并发送电子邮件 - Joel Deleep
如果Test.objects.filter(email_address__iexact=email, is_active=True)不存在: - Aleksei Khatkevich
还有一个疑问,你提到的代码应该在视图中使用还是表单中使用? - Joel Deleep
你最好阅读Django文档中关于PasswordResetView的内容,并从那里开始。 - Aleksei Khatkevich

1

我使用了itsdangeroussignals的帮助来完成这个任务。

token_generator.py

import hashlib
from typing import NoReturn, Union

from django.conf import settings
from itsdangerous import URLSafeTimedSerializer
from itsdangerous.exc import BadTimeSignature, SignatureExpired

serializer = URLSafeTimedSerializer(settings.SECRET_KEY, salt="active-email")
serializer.default_signer.default_digest_method = hashlib.sha256

MAX_AGE : Final = 60 * 60 * 3 # The token is valid for just 3 hours


class ExpiredToken(Exception):
    pass


class BadToken(Exception):
    pass


def generate_token(user_id: int) -> bytes:
    return serializer.dumps(user_id)


def validate_token(token: Union[str , bytes], max_age: int = MAX_AGE) -> Union[int, NoReturn]:
    try:
        data = serializer.loads(token, max_age=max_age)
    except SignatureExpired:
        raise ExpiredToken("Token has expired. request for another token.")
    except BadTimeSignature:
        raise BadToken("Token is invalid.")
    return data

我只是使用用户的pk并用itsdangerous对其进行签名。


signals.py

from django.conf import settings
from django.core.mail import send_mail
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.template.loader import render_to_string

from .token_generator import generate_token


@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def send_verify_email(instance, created, **kwargs):
    if not created or instance.is_active:
        return
    domain = settings.ALLOWED_HOSTS[0] or 'http://localhost:8000'
    token = generate_token(instance.id)
    message = render_to_string(
        "users/verify_email.html",
        context={"token": token, "base_url": domain},
    )
    send_mail(
        "Verify Email Subject",
        message=message,
        from_email=settings.DEFAULT_FROM_EMAIL,
        recipient_list=[instance.email],
    )

settings.py

DEFAULT_FROM_EMAIL = 'no-replay@mysite.com'

verify_email.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>title</title>
</head>

<body>
    <a href="{{base_url}}{% url 'verify-email' %}?token={{ token }}">verify</a>
</body>

</html>

views.py

from django.contrib.auth import get_user_model
from .token_generator import BadToken , ExpiredToken , validate_token
rom django.views import View

class EmailVerify(View):

    def get(self, request : HttpRequest) -> HttpResponse:
        UserModel = get_user_model()
        token = request.GET.get('token')
        if not token:
            return HttpResponseBadRequest('Error')
        try:
            user_id = validate_token(token)
        except BadToken:
            return HttpResponseBadRequest('Error')
        except ExpiredToken:
            return HttpResponseBadRequest('Error')
        user = UserModel.objects.get(pk=user_id)
        if user.email_verified:
            return HttpResponseBadRequest('Error')
        user.is_active = True
        user.save()
        return HttpResponse('Your account has been activated')

urls.py


from django.urls import path

from . import views as userview

urlpatterns = [
    path("verify-email/", userview.EmailVerify.as_view(), name="verify-email"),
]


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