详解从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脚本生成随机IP的简单方法
Jul 30 Python
使用Python脚本实现批量网站存活检测遇到问题及解决方法
Oct 11 Python
Python实现按当前日期(年、月、日)创建多级目录的方法
Apr 26 Python
浅谈tensorflow1.0 池化层(pooling)和全连接层(dense)
Apr 27 Python
python实现比较文件内容异同
Jun 22 Python
windows下python 3.6.4安装配置图文教程
Aug 21 Python
Python 数值区间处理_对interval 库的快速入门详解
Nov 16 Python
利用Python+阿里云实现DDNS动态域名解析的方法
Apr 01 Python
Python 占位符的使用方法详解
Jul 10 Python
Python制作词云图代码实例
Sep 09 Python
Python动态导入模块和反射机制详解
Feb 18 Python
matplotlib绘制鼠标的十字光标的实现(内置方式)
Jan 06 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
PHP中UNIX时间戳和日期间的转换与计算实例
2014/11/19 PHP
php使用sql server验证连接数据库的方法
2014/12/25 PHP
PHP实现的AES 128位加密算法示例
2019/09/16 PHP
laravel实现Auth认证,登录、注册后的页面回跳方法
2019/09/30 PHP
Yii框架 session 数据库存储操作方法示例
2019/11/18 PHP
父窗口获取弹出子窗口文本框的值
2006/06/27 Javascript
深入理解JavaScript系列(14) 作用域链介绍(Scope Chain)
2012/04/12 Javascript
IFrame跨域高度自适应实现代码
2012/08/16 Javascript
JS不间断向上滚动效果代码
2013/12/25 Javascript
javascript客户端遍历控件与获取父容器对象示例代码
2014/01/06 Javascript
基于JavaScript获取鼠标位置的各种方法
2015/12/16 Javascript
自动完成的搜索框javascript实现
2016/02/26 Javascript
每日十条JavaScript经验技巧(一)
2016/06/23 Javascript
jQuery实现复选框的全选和反选
2017/02/02 Javascript
Vue表单输入绑定的示例代码
2018/11/01 Javascript
小程序实现列表点赞功能
2018/11/02 Javascript
微信小程序实现卡片左右滑动效果的示例代码
2019/05/01 Javascript
Vue项目结合Vue-layer实现弹框式编辑功能(实例代码)
2020/03/11 Javascript
[48:00]EG vs LGD 2018国际邀请赛淘汰赛BO3 第二场 8.26
2018/08/29 DOTA
Python使用面向对象方式创建线程实现12306售票系统
2015/12/24 Python
Python无损音乐搜索引擎实现代码
2018/02/02 Python
python 日期操作类代码
2018/05/05 Python
python3 实现一行输入,空格隔开的示例
2018/11/14 Python
Django Celery异步任务队列的实现
2019/07/24 Python
python lambda函数及三个常用的高阶函数
2020/02/05 Python
使用Python求解带约束的最优化问题详解
2020/02/11 Python
希尔顿酒店官方网站:Hilton Hotels
2017/06/01 全球购物
The North Face北面德国官网:美国著名户外品牌
2018/12/12 全球购物
澳洲最大的时尚奢侈品电商平台:Cettire
2020/06/15 全球购物
一些PHP的面试题
2015/05/06 面试题
以下为Windows NT 下的32 位C++程序,请计算sizeof 的值
2016/12/07 面试题
英语教学随笔感言
2014/02/20 职场文书
幼儿园大班开学教师寄语
2014/04/03 职场文书
群众路线对照检查材料思想汇报怎么写
2014/09/18 职场文书
县委常委班子专题民主生活会查摆问题及整改措施
2014/09/27 职场文书
博士生专家推荐信
2015/03/25 职场文书