Filosfino

观察世界,出走青铜时代、梦马臆想

Django 弱密码校验那些事

发布于 # Django # Tech

弱密码校验是 Django 1.9 新功能之一,可以通过 settings 中的 AUTH_PASSWORD_VALIDATORS 来开启。

以下是四个自带的校验器,当然也可以自己写校验器,只需要按照下述格式添加进去就可以做统一校验。

# settings.py
AUTH_PASSWORD_VALIDATORS = [
    {
        # 用户属性相似验证,检查密码和一组用户的属性的相似性
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        # 最小长度验证,最小接受长度为 9
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
        'OPTIONS': {
            'min_length': 9,
        }
    },
    {
        # 常见密码验证,这个检查器会对比常用的弱密码,这些常用密码被 gzip 打包储存在 `django/contrib/auth/common-passwords.txt.gz` 中
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        # 纯数字密码验证
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

并不是直接 set_password 就会校验,而是需要在 set_password 前手动调用,用法如下

from django.contrib.auth.password_validation import validate_password

validate_password(password, user=None, password_validators=None)

其中

如果所有 validator 都通过,返回 None;否则 raise 包含所有错误消息的 ValidationError。

实战:在 form 中校验用户的两次密码输入

class PasswordInitForm(forms.Form):
    # RepeatCharField 是自定义的一个 MultiValueField
    # 它接受两个 CharField 输入,并检查其是否一致
    # 如果不一致,raise ValidationError;否则返回相同的 CharField 值
    password = RepeatCharField()

    def clean_password(self):
        validate_password(self.cleaned_data['password'])
        return self.cleaned_data['password']

我的 view 是这样的

class PasswordInitView(LoginRequiredMixin, TemplateView):
    template_name = 'user/password_init.html'

    @json_resp()
    def post(self, request):
        form = PasswordInitForm(request.POST)
        result = {}
        if form.is_valid():
            request.user.set_password(form.cleaned_data['password'])
            request.user.need_reset_password = False
            request.user.save()
            result['next'] = get_redirect_url(request, 'POST')
        else:
            result = Error(form.errors)
        return result

附上 RepeatCharField 的代码

class RepeatCharWidget(MultiWidget):
    def __init__(self):
        widgets = (
            TextInput(),
            TextInput(),
        )
        super(RepeatCharWidget, self).__init__(widgets)


class RepeatCharField(MultiValueField):
    def __init__(self, *args, **kwargs):
        fields = (
            CharField(),
            CharField(),
        )
        if 'error_messages' not in kwargs or 'invalid' not in kwargs.get('error_messages'):
            if 'error_messages' not in kwargs:
                kwargs['error_messages'] = {}
            kwargs['error_messages'].update({'invalid': ugettext_lazy('Invalid repeat input')})

        kwargs['widget'] = kwargs.pop('widget', RepeatCharWidget())
        super(RepeatCharField, self).__init__(fields, *args, **kwargs)

    def compress(self, data_list):
        return data_list[0]

    def clean(self, value):
        if value[0] != value[1]:
            raise forms.ValidationError(getattr(self, 'error_messages', {}).get('invalid', ugettext_lazy('Repeat input should be the same')))
        return super(RepeatCharField, self).clean(value)