自定义认证后台

2017-08-0725422 阅读49 评论

Django auth 应用默认支持用户名(username)进行登录。但是在实践中,网站可能还需要邮箱、手机号、身份证号等进行登录,这就需要我们自己写一个认证后台,用于验证用户输入的用户信息是否正确,从而对拥有正确凭据的用户进行登录认证。

Django 验证用户合法性的方式

Django 对用户登录的验证工作均在一个被称作认证后台(Authentication Backend)的类中进行。这个类是一个普通的 Python 类,它有一个 authenticate 方法,接收登录用户提供的凭据(如用户名或者邮箱以及密码)作为参数,并根据这些凭据判断用户是否合法(即是否是已注册用户,密码是否正确等)。下面是 Django 内置的认证后台的部分源代码,从代码中可以清晰地看到其工作方式:

django.contrib.auth.backends

class ModelBackend(object):
    """
    Authenticates against settings.AUTH_USER_MODEL.
    """

    def authenticate(self, request, username=None, password=None, **kwargs):
        if username is None:
            username = kwargs.get(UserModel.USERNAME_FIELD)
        try:
            user = UserModel._default_manager.get_by_natural_key(username)
        except UserModel.DoesNotExist:
            # Run the default password hasher once to reduce the timing
            # difference between an existing and a non-existing user (#20760).
            UserModel().set_password(password)
        else:
            if user.check_password(password) and self.user_can_authenticate(user):
                return user

这段代码根据用户传入的 username 和 password,验证该 username 对应的用户是否存在以及密码是否正确,是则返回该 user 对象。

可以定义多个认证后台,Django 内部会逐一调用这些后台的 authenticate 方法来验证用户提供登录凭据的合法性,一旦通过某个后台的验证,表明用户提供的凭据合法,从而允许登录该用户。

Email Backend

在本示例项目中,用户注册时需要填写邮箱。因为 Django auth 应用内置只支持用户名和密码的认证方式,所以目前用户是无法使用 Email 进行登录的。为了实现邮箱登录,我们需要编写一个认证后台。这个后台的作用便是验证用户提供的凭据(这里是邮箱以及密码)是合法的,完全仿照内置的 ModelBackend 代码即可。首先在 users 应用下新建一个 backends.py 文件,然后写入如下代码:

users/backends.py

from .models import User

class EmailBackend(object):
    def authenticate(self, request, **credentials):
        # 要注意登录表单中用户输入的用户名或者邮箱的 field 名均为 username
        email = credentials.get('email', credentials.get('username'))
        try:
            user = User.objects.get(email=email)
        except User.DoesNotExist:
            pass
        else:
            if user.check_password(credentials["password"]):
                return user

    def get_user(self, user_id):
        """
        该方法是必须的
        """
        try:
            return User.objects.get(pk=user_id)
        except User.DoesNotExist:
            return None

逻辑非常简单,就是根据用户提供的 Email 和密码,检查该 emai 对应的用户是否存在,如果存在则检查密码是否正确,如果密码也没有问题,则返回该 user 对象。

配置 Backend

接下来就要告诉 Django,需要使用哪些 Backends 对用户的凭据信息进行验证,这需要在 settings.py 中设置:

settings.py

AUTHENTICATION_BACKENDS = (
    'django.contrib.auth.backends.ModelBackend',
    'users.backends.EmailBackend',
)

第一个 Backend 是 Django 内置的 Backend,当用户提供的是用户名和正确的密码时该 Backend 会通过验证;第二个 Backend 是刚刚自定义的 Backend,当用户提供的是 Email 和正确的密码时该 Backend 会通过验证。

测试

在登录界面输入注册时的邮箱和正确的密码,可以发现也可以登录成功了,说明我们自定义的 Backend 是有效的。大功告成!

总结

本教程的示例项目代码位于 GitHub:Django Auth Example

如果遇到问题,请通过下面的方式寻求帮助。

更多 Django 相关教程,请访问我的个人博客:追梦人物的博客

-- EOF --

49 评论
登录后回复
HuangChangHuai
2020-06-29 23:27:25

不知道为什么总是觉得用户认证这一方面实践起来,程序运行得也很卡,

另外,博主图片得源失效了

回复
templeydy
2019-11-20 12:43:10

博主,该优化了,网站太卡了

回复
suxianglun
2018-09-28 22:37:48

博主,前前后后断断续续花费了两个月时间看完了你的前两个教程,写的很好,遇到的问题基本都能在评论里找到解决办法,非常感谢博主。自定义后台这篇文章中 user = User.objects.get(email=email)应该是写成user = User.objects.get(email=email)[0] ,前者user是一个QuerySet 即对象列表,再使用user.check_password()时候会报错'QuerySet' object has no attribute 'check_password',后者user是一个User对象了,就可以调用check_password函数了,自己实践的时候发现的一个问题。

回复
filwaline suxianglun
2020-01-19 14:53:15

你这个肯定是搞错了什么,get返回的一定是一个modelobject(这里是User),而不是QuerySet。
参考 Django的文档 https://docs.djangoproject.com/en/2.2/topics/db/queries/#retrieving-a-single-object-with-get
如果get的筛选条件命中了多个UserObject,那就会直接抛出MultipleObjectsReturned异常,而不是返回QuerySet
你可能是使用了filter或者all才得到QuerySet,并且需要用索引来找到具体的对象。

回复
fbckf
2018-07-20 10:31:31

User 模型中 email 字段需要设置 unique=True ,否则注册时邮箱可重复,注册账户使用同一个邮箱会在密码重置和邮箱认证登录处出现 bug

回复
SamK6517433923 fbckf
2019-02-27 14:05:07

unique = True 这个方法可以,比在表单中添加 clean_email 方法验证表单要好使

回复
linoer
2018-07-10 14:35:52

自定义的backend和后台混掉了,找了半天问题,知道发现博主的

AUTHENTICATION_BACKENDS = (
    'django.contrib.auth.backends.ModelBackend',
    'users.backends.EmailBackend',
)

原来是漏掉了内置backend

感谢

回复
-Ni
2018-05-27 20:37:21

博主我发现这个项目的后台http://127.0.0.1:8080/admin登陆的时候会发生错误。是怎么回事,要如何修复?

回复
-Ni
2018-05-24 11:38:17

希望博主可以添加一些test case和写test的技巧.平时想写test但是request不会构造

回复
追梦人物 -Ni
2018-05-28 12:01:40

嗯,这个建议很好,在完善教程的时候我会考虑添加一些测试的技巧

回复
SamK6517433923 追梦人物
2019-02-27 14:14:16

期待

回复
yuqiangwu
2018-03-15 17:21:08

有一个问题就是,我注册的时候不同的账号但是注册邮箱填相同的,也是能够注册成功的,然后我用自定义的邮箱账号去认证就会报数据不只有一条, 请问如何在注册的时候添加一个验证邮箱是否已经注册呢。

回复
yiwozai yuqiangwu
2018-03-22 10:48:15

覆盖AbstractUser.email字段,可以这样定义:

email = models.EmailField('邮箱', unique=True, error_messages={ 'unique': "该邮箱地址已被占用。",},)

回复
pidada yuqiangwu
2019-04-26 14:07:14

注册不同的账号你能够成功吗?我用同一个邮箱好像不成功的!直接提示:用该邮箱注册的用户已经存在

回复
shaunsyb_weibo
2018-03-07 17:40:59

youyong ~~~~~~

回复
yawuplus
2018-02-19 17:40:43

请问下博主,你的博客如何退出登录呢?

回复
追梦人物 yawuplus
2018-04-13 17:52:48

Sorry,目前暂时不支持注销登录!

回复
zzfeng2012
2018-02-01 18:09:33

前面几节都正常,这一节没实现成功,可是这一节也就只编辑backends.py 和 settings这两个文件。用邮箱登录时不成功,提示“登录请输入一个正确的 用户名 和密码.”,不知道是哪里出了问题。

我把github上的 “Django Auth Example” 这个例子拿下来在本地试的也是用邮箱登录不了。

回复
追梦人物 zzfeng2012
2018-04-19 14:24:16

你的邮箱输入不正确吧?

回复
jamswu 追梦人物
2019-04-24 15:59:11

000

回复
各种迷惘的大明
2018-01-29 08:42:13

博主,问你个问题,就是怎么避免提交评论的时候重复提交呢???

回复
魚_Drowning
2017-10-03 17:00:13

楼楼,我把源码包下载下来跑,在登录或者注册页面 填好资料 注册后, 提交后,就跳到Error界面,显示:no such table: users_user   请问这个怎么解决

回复
曼彻斯特抬价联队 魚_Drowning
2017-11-02 09:29:15

数据库没有弄好吧。

$ python manage.py makemigrations

$ python manage.py migrate

回复
Jeremy Zheng
2017-09-09 10:48:46

博主你好,

我在做一个C2C的网站,每个购买者和卖家 都有各自的权限。 我想向您请教一下auth这块更深入的应用的问题。关于限制用户各自的权限及页面保护之类的功能, 根使用django-guardian + admin 比较好,还是自己重新造轮子。博主请问您怎么看。希望您能给点意见。谢谢!

回复
追梦人物 Jeremy Zheng
2017-09-11 12:43:32

看自己的需求了,gardian 我没用过。

回复
striderhcx
2017-09-03 20:33:13

博主你好:

突然发现一个bug,邮箱居然可以一样,我用相同的邮箱注册了3个账户。然后导致我 user = User.objects.get(email=email)的时候返回了三个user,这个怎么保证email的唯一性呢是,需要

修改注册表单继承的UserCreationForm源码?

还有 email = credentials.get('email', credentials.get('username'))这个如果获取不到email的值,为什么默认返回username的值?

回复
追梦人物 striderhcx
2017-09-03 21:37:05

1. django 内置的 model 定义时 email 默认可以重复。考虑到难以修改 model 的定义,且不是一个好方法。我们可以通过复写表单的验证方法(例如 clean_email)来验证 email 的唯一性,具体请参考 django 关于 form 数据验证的文档。

2. email = credentials.get('email', credentials.get('username')),请参考 python dict 对象的 get 方法。具体来说,dict.get('key'),如果 key 不错在,则放回 None。也可以设置 key 不存在时的返回值,dict.get('key',value),当 key 不存在时,会返回 value。

回复
追梦人物
2017-09-02 19:27:41

略微复杂,暂时无这个计划。

回复
CodeZsx
2017-09-01 17:21:58

博主,集成github登录、微博登录等第三方登录的教程什么时候出一个啊

回复
-陈思煜
2017-08-31 00:01:50

博主,请问怎么使用微博登陆

回复
追梦人物 -陈思煜
2017-08-31 10:27:33

你可参考 django-allauth 第三方包,提供了很棒的社交账户登录支持。

回复
-陈思煜 追梦人物
2017-09-01 00:13:54

恩恩,好的

回复
chenyufei91
2017-08-28 16:22:40

不知道为什么我登陆登陆不上,将def authenticate(self, request, **credentials):中的request去掉,我就好了。请问博主这里的request起到什么作用? 我这里的request也是灰的,没有被用到~ O(∩_∩)O谢谢- -

回复
追梦人物 chenyufei91
2017-08-28 16:51:47

必须使用 1.11 以上版本,否则去掉 request 参数

回复
chenyufei91 追梦人物
2017-08-28 21:48:46

谢博主。博主能讲下你这个二层评论系统是怎么做的吗?能讲下思路吗- -

回复
追梦人物 chenyufei91
2017-08-29 16:29:07

你可以参考 django-mptt,这个包可以让你轻松地生成一个树形结构的模型。

回复
李正
2017-08-25 16:49:41

感谢博主

回复
mozhemeng
2017-08-21 13:55:05

博主好,我想问一下,auth这块更深入的应用,比如权限及页面保护(就是有A权限的用户只能访问A页面,有B权限的用户只能访问B页面)之类的功能,应该从哪里下手呢,有点迷茫呀

回复
追梦人物 mozhemeng
2017-08-21 20:07:54

这种权限好像 django 自带的权限系统控制不了,读一读 django guardian 的文档看看有没有解决方案?否则可能需要自己写权限控制系统了。

回复
mozhemeng 追梦人物
2017-08-22 15:59:23

谢谢博主指点,我还想问一下,在一般项目中,用户认证这一块是不是都是单独放在一个app里的,其他功能再开app,这个认证状态是跨app都可用的吗

回复
追梦人物 mozhemeng
2017-08-22 17:20:16

是的,一个功能做成一个app,这是 django 推荐做法。

回复
周维
2017-08-10 11:41:13

博客 博客进阶 用户系统这三个教程对我这样的python初学者非常有帮助!
已经部署好了自己的博客. 体验到了django的强大便捷.
非常感谢作者!!!
我是一名iOS开发者 期望作者能出一些 django 写 api 的教程.
例如 django 的用户系统在web端做了很多工作, 这些特性能不能用于客户端?

回复
追梦人物 周维
2017-08-10 18:10:11

django-rest-framework 可以为 django 项目提供 restful API,用于 ios 或者 android 项目调用。需要一定的学习成本。

回复
周维 追梦人物
2017-08-15 23:16:41

嗯 这几天正在看django-rest-framework!
跟django一样也是自动化了好多工作~
果然是
人生苦短 我用Python 😂

回复
追梦人物 周维
2017-08-16 10:45:16

赞!关于 API 的开发我其实也还没涉及过。

回复
rookie250 周维
2017-11-21 14:22:55

我也是,希望自己会用django 做后端api ,可以加个微信交流一下么? 

回复
toSummerDawn
2017-08-07 16:47:18

很棒的教程。非常期待融合第三方验证的Backend。

回复
追梦人物 toSummerDawn
2017-08-07 19:41:13

thanks! 抽空我写一些 django-allauth 的使用方法,当然网上也已经有很多类似教程。

回复
Hopetree 追梦人物
2017-09-07 23:14:44

- 期待博主的第三方认证登录教程的到来!!

- 博主讲的很清晰,新手一看就懂

回复