详解从Django Allauth中进行登录改造小结


Posted in Python onDecember 18, 2019

大概来介绍一下 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 一份流程,这样才能实现平稳降级和无痛迁移。

查找账号

  1. 获取用户授权信息中的 uid
  2. 在 AllauthSocialAccount 表中获取到对应的数据,如果没有则返回 None

登录流程

  1. 确保用户是匿名用户:request.user.is_anouymous 且已经存在对应的账号
  2. 更新 AllauthSocialAccount 表中的数据到最新
  3. 根据 social account 更新 social token
  4. 写入 session(Django 中自带 login 函数)

注册流程

  1. 确保用户是匿名用户且不存在对应账号
  2. 创建新用户(要点是生成用户名和昵称),在 Django 中有 create_user 可以直接创建
  3. 写入 AllatuhSocialAccount 和 AllauthSocialToken
  4. 写入 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 表中,这样就能保证对应的关系了。

总结

在账号的部分由于还没有改完,所以可以说的不多,只是做了一些微小的工作,对于这种可能需要根据国情定制的需求,建议大家还是小心使用。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
Python使用random和tertools模块解一些经典概率问题
Jan 28 Python
python统计字符串中指定字符出现次数的方法
Apr 04 Python
python实现在每个独立进程中运行一个函数的方法
Apr 23 Python
python中zip和unzip数据的方法
May 27 Python
在CentOS上配置Nginx+Gunicorn+Python+Flask环境的教程
Jun 07 Python
Python numpy生成矩阵、串联矩阵代码分享
Dec 04 Python
python简单实现操作Mysql数据库
Jan 29 Python
对pycharm代码整体左移和右移缩进快捷键的介绍
Jul 16 Python
代码详解django中数据库设置
Jan 28 Python
django认证系统 Authentication使用详解
Jul 22 Python
基于python图像处理API的使用示例
Apr 03 Python
python四个坐标点对图片区域最小外接矩形进行裁剪
Jun 04 Python
解决pycharm最左侧Tool Buttons显示不全的问题
Dec 17 #Python
python 字段拆分详解
Dec 17 #Python
从pandas一个单元格的字符串中提取字符串方式
Dec 17 #Python
基于pandas中expand的作用详解
Dec 17 #Python
Python使用psutil获取进程信息的例子
Dec 17 #Python
python psutil监控进程实例
Dec 17 #Python
查看端口并杀进程python脚本代码
Dec 17 #Python
You might like
JAVA/JSP学习系列之六
2006/10/09 PHP
PHP XML操作的各种方法解析(比较详细)
2010/06/17 PHP
php简单浏览目录内容的实现代码
2013/06/07 PHP
PHP统计nginx访问日志中的搜索引擎抓取404链接页面路径
2014/06/30 PHP
PHP文件锁定写入实例解析
2014/07/14 PHP
CI(CodeIgniter)模型用法实例分析
2016/01/20 PHP
php检查函数必传参数是否存在的实例详解
2017/08/28 PHP
借用Google的Javascript API Loader来加速你的网站
2009/01/28 Javascript
使用jQuery的将桌面应用程序引入浏览器
2010/11/19 Javascript
再论Javascript的类继承
2011/03/05 Javascript
javascript倒计时功能实现代码
2012/06/07 Javascript
javascript基础知识大全 便于大家学习,也便于我自己查看
2012/08/17 Javascript
动态创建script标签实现跨域资源访问的方法介绍
2014/02/28 Javascript
JS动态日期时间的获取方法
2015/09/28 Javascript
Angularjs整合微信UI(weui)
2016/03/15 Javascript
很棒的js选项卡切换效果
2016/07/15 Javascript
vscode中的vue项目报错Property ‘xxx‘ does not exist on type ‘CombinedVueInstance<{ readyOnly...Vetur(2339)
2020/09/11 Javascript
vue解决跨域问题(推荐)
2020/11/10 Javascript
[22:59]VGJ.S vs VG 2018国际邀请赛小组赛BO2 第二场 8.16
2018/08/17 DOTA
[01:50:49]DOTA2-DPC中国联赛 正赛 PSG.LGD vs Aster BO3 第三场 1月24日
2021/03/11 DOTA
python读取Android permission文件
2013/11/01 Python
Python的dict字典结构操作方法学习笔记
2016/05/07 Python
Python实现批量更换指定目录下文件扩展名的方法
2016/09/19 Python
浅谈keras的深度模型训练过程及结果记录方式
2020/01/24 Python
Python中实现输入一个整数的案例
2020/05/03 Python
Python学习笔记之装饰器
2020/08/06 Python
python PyAUtoGUI库实现自动化控制鼠标键盘
2020/09/09 Python
英国买鞋网站:Charles Clinkard
2019/11/14 全球购物
大学生就业策划书范文
2014/04/04 职场文书
平安工地建设方案
2014/05/06 职场文书
2014年干部作风建设总结
2014/10/23 职场文书
2014年学生会部门工作总结
2014/11/07 职场文书
贷款承诺书
2015/01/20 职场文书
《桂花雨》教学反思
2016/02/19 职场文书
爱国之歌(8首)
2019/09/29 职场文书
python中Tkinter 窗口之输入框和文本框的实现
2021/04/12 Python