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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 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 前手动调用,用法如下

1
2
3
from django.contrib.auth.password_validation import validate_password

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

其中

  • password 是 raw string 的 password 字符串
  • user 是检查的用户实例,非必须。如果 validator 需要根据 user 实例做判断,那么就需要传
  • password_validators 是 list,包含所有需要校验的 validator,不指定的话默认校验所有 settings.py 中指定的 validator

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

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

1
2
3
4
5
6
7
8
9
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 是这样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 的代码

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
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)