详解从Django Allauth中进行登录改造小结
作者:敖天羽 发布时间:2021-05-05 14:26:58
大概来介绍一下 Django Allauth 改造的期间遇到的一些问题和改造方法,在此之前我只想说——Django Allauth 是屑。
为什么我说 Django Allauth 是屑
入职之初我就接到了一些第三方登录的任务,然而 Django Allauth 将内部封装的太好,暴露的 API 不足,更新又慢,issue 和 PR 很少有人处理,当你需要扩展时,很多情况下你只能用一些 hack 的手段去解决问题,非常蛋疼,所以当时就决定慢慢的切到自己的一套 Auth 体系中。
目前已经做的是第三方登录的部分,账号管理的部分还没有迁移,之前稍微看了一下,要迁移的成本还是比较麻烦的。
迁移成本在哪里
Django 中的账号密码登录一般是由本身提供的 auth 表进行扩展的结果,而 allauth 在此基础上扩充了第三方登录的几个表,再和本身的 auth user 表关联。而这一部分是构建在 Allauth 内部的 model 内,且没有暴露任何的方法来修改结构(当然可能也是因为真的不好改),导致一旦不满足需求就很难搞,因为数据已经放在那里了,刷数据同步的方案对于大流量网站来说也并不是很友好的选择。
此外,在路由上,由于我们需要尽可能的无痛迁移和在渐进式切换时的平稳降级,因此只能通过简单粗暴的路由覆盖操作,这极度依赖路由的解析顺序。
数据库扩展与 provider 变更
说了这么多,其实关键点并不在于「问题在哪里」,而在于「我是怎么解决这些问题的」。
Allauth 一个平台的注册是一个 provider,比如 「wechat」、「weibo」、「qq」,整张表是一对一的关系,那么问题来了,我们知道,国内的平台往往并不是一个 appid 和 key 能搞定的事情,对于 web 和移动端的平台来说,其实是两个 appid 共享一套 unionid,尽管官方提供了一套增加 Provider 的扩展方式,但实际上是没有必要的,因为 Web 和移动端来说,获得用户信息的接口是共享的,而移动端并不用通过后端获取 access_token。在绑定上,实际上也是同一个平台。
因此我们扩充了一张表来解决这个问题,将我们额外的信息放在了额外添加的表中。
之后要解决的就是 admin 的 provider select 问题,它会进行一次校验,所以我们必须要取消这些校验并把 select 改成 input。
首先,我们要取消 Model 层的校验, Proxy 可以对表进行一些覆盖式的操作(但不能改变表结构):
class CustomSocialApp(SocialApp):
class Meta:
proxy = True
def clean_fields(self, exclude=None):
# 别校验了
pass
def full_clean(self, exclude=None, validate_unique=True):
# 别校验了
pass
def clean(self, exclude=None, validate_unique=True):
# 别校验了
pass
这里我们在原来的 SocialApp 的基础上新建一个属于自己的新的 Admin,他本质上还是操作 SocialApp 表,只是挪出来方便我们自定义而已:
class CustomSocialAppAdmin(SocialAppAdmin):
list_display = ('provider_text', 'name')
form = CustomAppAdminForm
def get_form(self, request, obj=None, **kwargs):
kwargs['widgets'] = {'provider': forms.TextInput}
return super().get_form(request, obj, **kwargs)
def provider_text(self, obj):
return obj.provider
但是这样就会遇到一个 provider 的校验问题,这也就是上面我们还没有写完的 CustomAppAdminForm 的部分,我们将校验的部分用自定义的 form 完全取消:
class CustomSocialAppAdminForm(forms.ModelForm):
class Meta:
model = CustomSocialApp
fields = '__all__'
widgets = {'provider': forms.TextInput()}
def clean(self):
# 别校验了
if self.has_error('provider'):
del self._errors['provider']
self.cleaned_data['provider'] = self.data['provider']
return self.cleaned_data
这样就完成了校验的修改,成了一个完全体的 input 覆盖了原来的 select。
第三方登录与绑定流程
上面可以任意在表中拓展 provider 了 ,但重头戏其实是:搞清楚 allauth 原本的登录和绑定流程,完美的 copy 一份流程,这样才能实现平稳降级和无痛迁移。
查找账号
获取用户授权信息中的 uid
在 AllauthSocialAccount 表中获取到对应的数据,如果没有则返回 None
登录流程
确保用户是匿名用户:request.user.is_anouymous 且已经存在对应的账号
更新 AllauthSocialAccount 表中的数据到最新
根据 social account 更新 social token
写入 session(Django 中自带 login 函数)
注册流程
确保用户是匿名用户且不存在对应账号
创建新用户(要点是生成用户名和昵称),在 Django 中有 create_user 可以直接创建
写入 AllatuhSocialAccount 和 AllauthSocialToken
写入 session 登录
绑定流程
用户不是匿名用户
查找对应的第三方账号是否已经被绑定
更新 AllauthSocialAccount 表
更新 social token
只要按照这个流程实现下来就可以了,而同一平台多 provider(appid)的差异功能与核心部分无关,可以在各社交媒体对应的文件中单独实现。
构建新的账号系统
现在我们彻底将第三方登录抽离了出来,接下来需要抽出账号的部分,账号登录和注册本质上还是 Django 提供的那些东西,因此比较好抽,需要兼容的部分主要在于「忘记密码」和「重置密码」。
我们来思考一下为什么这部分需要做兼容:
一般来说我们都是在重置密码时在手机或者邮箱里收到一个验证邮件,里面会附上一个随机字符串用来保证连接的唯一性。而在我们替换过程中,我们不能让一群用户已经发送过但还没有使用的随机字符串不可用,从可读的角度来看,生成的内容也应该和原来差不多(同时也是避免冲突),因此需要抄一下它的忘记密码。
在 account/forms 中表明了 token 的生成算法:
from django.contrib.auth.tokens import PasswordResetTokenGenerator
token_generator = PasswordResetTokenGenerator()
# 生成 token
key = token_generator.make_token(user)
# 检查 token
token_generator.check_token(user, key)
Allauth 中将 user 用 base36 加密了,兼容 Python2,所以 utils 中的语句略长,由于我们直接是 Python3,所以只剩下这些句子:
from django.utils.http import base36_to_int
from django.utils.http import int_to_base36
def user_pk_to_url_str(user):
return int_to_base36(user.pk)
def url_str_to_user_pk(s):
return base36_to_int(s)
所有内容将会被存储在 account_emailconfirmation 表中,这样就能保证对应的关系了。
总结
在账号的部分由于还没有改完,所以可以说的不多,只是做了一些微小的工作,对于这种可能需要根据国情定制的需求,建议大家还是小心使用。
来源:https://www.codesky.me/archives/django-allauth-summary.wind
猜你喜欢
- 许多人在数据科学、机器学习、web开发、脚本编写和自动化等领域中都会使用Python,它是一种十分流行的语言。Python流行的部分原因在于
- 一、python对json的支持从python2.6开始,python标准库中添加了对json的支持,操作json时,只需要import j
- 模块Python 模块(Module),是一个 Python 文件,以 .py 结尾,包含了 Python 对象定义和 Python 语句模
- 一、前言在学习深度学习会发现都比较爱用python这个argparse,虽然基本能理解,但没有仔细自己动手去写,因此这里写下来作为自己本人的
- 1.什么是ORMORM 全拼Object-Relation Mapping.中文意为 对象-关系映射.在MVC/MVT设
- 我就废话不多说了,直接上代码吧!# -*- coding:utf8 -*-import paho.mqtt.client as mqttfr
- 下面看下通过Pyinstaller打包Pygame库写的小游戏程序出现的问题解决方法# -基于Python的Pygame库的GUI游戏游戏内
- 以下实例用于判断一个数字是否为奇数或偶数:# -*- coding: UTF-8 -*-# Filename : test.py# Pyth
- 个人总结了在开发css框架中的一点经验,献丑了。希望大家的讨论能使我们共同进步。:)1、css框架中国的互联网行业已经发展了10年,浏览器也
- 上一篇实战爬取知乎热门话题的实战,并且保存为本地的txt文本先上代码,有很多细节和坑需要规避,弄了两个半小时import requestsi
- 根据微软论坛作者的英文解释,.NET framework 4.0 安装失败回滚貌似是因为“msvcr100_clr0400.d
- 逻辑门是任何数字电路的基本构建块。它需要一两个输入并根据这些输入产生输出。输出可能为高 (1) 或低 (0)。逻辑门使用二极管或晶体管实现。
- 一、在访客的内心深处做导航我讨厌迷失,不管是在道路上或是在线网络上。猜想一下?您的访客也是这样的。就像我们期望看到的道路上的路标一样,来帮助
- 如何将123456789转化成123,456,789这样的形式呢?很多流量大的站比如优酷都有这样的格式。也是设计程序最常用的算
- 一:修改文件上传语言为PHP 打开fckconfig.js 找到: var _FileBrowserLanguage = 'asp&
- 目录实现思路使用BackgroundSubtractorMOG2进行背景分割使用人像识别填充面部信息使用形态学填充分割出来的前景将人像与目标
- 起因前端日子写完的Python入库脚本,通过直接读取配置文件的内容(包含了数据库的ip,数据库的用户名,数据库的密码),因为配置文件中的数据
- 1、前言最近在做微信公众号开发在进行网页授权时,微信需要用户自己在授权url中带上一个类似token的state的参数,以防止跨站攻击。在经
- 教程使用的版本是2019.1新版本安装激活可以参考此篇教程,通用版!一、go安装1、建议去go语言中文网下载,网址:https://stud
- python语言本身没有提供const,但实际开发中经常会遇到需要使用const的情形,由于语言本身没有这种支出,因此需要使用一些技巧来实现