详解从Django Allauth中进行登录改造小结
大概来介绍一下DjangoAllauth改造的期间遇到的一些问题和改造方法,在此之前我只想说——DjangoAllauth是屑。
为什么我说DjangoAllauth是屑
入职之初我就接到了一些第三方登录的任务,然而DjangoAllauth将内部封装的太好,暴露的API不足,更新又慢,issue和PR很少有人处理,当你需要扩展时,很多情况下你只能用一些hack的手段去解决问题,非常蛋疼,所以当时就决定慢慢的切到自己的一套Auth体系中。
目前已经做的是第三方登录的部分,账号管理的部分还没有迁移,之前稍微看了一下,要迁移的成本还是比较麻烦的。
迁移成本在哪里
Django中的账号密码登录一般是由本身提供的auth表进行扩展的结果,而allauth在此基础上扩充了第三方登录的几个表,再和本身的authuser表关联。而这一部分是构建在Allauth内部的model内,且没有暴露任何的方法来修改结构(当然可能也是因为真的不好改),导致一旦不满足需求就很难搞,因为数据已经放在那里了,刷数据同步的方案对于大流量网站来说也并不是很友好的选择。
此外,在路由上,由于我们需要尽可能的无痛迁移和在渐进式切换时的平稳降级,因此只能通过简单粗暴的路由覆盖操作,这极度依赖路由的解析顺序。
数据库扩展与provider变更
说了这么多,其实关键点并不在于「问题在哪里」,而在于「我是怎么解决这些问题的」。
Allauth一个平台的注册是一个provider,比如「wechat」、「weibo」、「qq」,整张表是一对一的关系,那么问题来了,我们知道,国内的平台往往并不是一个appid和key能搞定的事情,对于web和移动端的平台来说,其实是两个appid共享一套unionid,尽管官方提供了一套增加Provider的扩展方式,但实际上是没有必要的,因为Web和移动端来说,获得用户信息的接口是共享的,而移动端并不用通过后端获取access_token。在绑定上,实际上也是同一个平台。
因此我们扩充了一张表来解决这个问题,将我们额外的信息放在了额外添加的表中。
之后要解决的就是admin的providerselect问题,它会进行一次校验,所以我们必须要取消这些校验并把select改成input。
首先,我们要取消Model层的校验, Proxy可以对表进行一些覆盖式的操作(但不能改变表结构):
classCustomSocialApp(SocialApp): classMeta: proxy=True defclean_fields(self,exclude=None): #别校验了 pass deffull_clean(self,exclude=None,validate_unique=True): #别校验了 pass defclean(self,exclude=None,validate_unique=True): #别校验了 pass
这里我们在原来的 SocialApp的基础上新建一个属于自己的新的Admin,他本质上还是操作SocialApp表,只是挪出来方便我们自定义而已:
classCustomSocialAppAdmin(SocialAppAdmin): list_display=('provider_text','name') form=CustomAppAdminForm defget_form(self,request,obj=None,**kwargs): kwargs['widgets']={'provider':forms.TextInput} returnsuper().get_form(request,obj,**kwargs) defprovider_text(self,obj): returnobj.provider
但是这样就会遇到一个provider的校验问题,这也就是上面我们还没有写完的 CustomAppAdminForm的部分,我们将校验的部分用自定义的form完全取消:
classCustomSocialAppAdminForm(forms.ModelForm): classMeta: model=CustomSocialApp fields='__all__' widgets={'provider':forms.TextInput()} defclean(self): #别校验了 ifself.has_error('provider'): delself._errors['provider'] self.cleaned_data['provider']=self.data['provider'] returnself.cleaned_data
这样就完成了校验的修改,成了一个完全体的input覆盖了原来的select。
第三方登录与绑定流程
上面可以任意在表中拓展provider了,但重头戏其实是:搞清楚allauth原本的登录和绑定流程,完美的copy一份流程,这样才能实现平稳降级和无痛迁移。
查找账号
- 获取用户授权信息中的 uid
- 在 AllauthSocialAccount表中获取到对应的数据,如果没有则返回 None
登录流程
- 确保用户是匿名用户:request.user.is_anouymous且已经存在对应的账号
- 更新AllauthSocialAccount表中的数据到最新
- 根据socialaccount更新socialtoken
- 写入session(Django中自带login函数)
注册流程
- 确保用户是匿名用户且不存在对应账号
- 创建新用户(要点是生成用户名和昵称),在Django中有 create_user可以直接创建
- 写入 AllatuhSocialAccount和 AllauthSocialToken
- 写入session登录
绑定流程
- 用户不是匿名用户
- 查找对应的第三方账号是否已经被绑定
- 更新AllauthSocialAccount表
- 更新socialtoken
只要按照这个流程实现下来就可以了,而同一平台多provider(appid)的差异功能与核心部分无关,可以在各社交媒体对应的文件中单独实现。
构建新的账号系统
现在我们彻底将第三方登录抽离了出来,接下来需要抽出账号的部分,账号登录和注册本质上还是Django提供的那些东西,因此比较好抽,需要兼容的部分主要在于「忘记密码」和「重置密码」。
我们来思考一下为什么这部分需要做兼容:
一般来说我们都是在重置密码时在手机或者邮箱里收到一个验证邮件,里面会附上一个随机字符串用来保证连接的唯一性。而在我们替换过程中,我们不能让一群用户已经发送过但还没有使用的随机字符串不可用,从可读的角度来看,生成的内容也应该和原来差不多(同时也是避免冲突),因此需要抄一下它的忘记密码。
在 account/forms中表明了token的生成算法:
fromdjango.contrib.auth.tokensimportPasswordResetTokenGenerator token_generator=PasswordResetTokenGenerator() #生成token key=token_generator.make_token(user) #检查token token_generator.check_token(user,key)
Allauth中将user用base36加密了,兼容Python2,所以utils中的语句略长,由于我们直接是Python3,所以只剩下这些句子:
fromdjango.utils.httpimportbase36_to_int fromdjango.utils.httpimportint_to_base36 defuser_pk_to_url_str(user): returnint_to_base36(user.pk) defurl_str_to_user_pk(s): returnbase36_to_int(s)
所有内容将会被存储在 account_emailconfirmation表中,这样就能保证对应的关系了。
总结
在账号的部分由于还没有改完,所以可以说的不多,只是做了一些微小的工作,对于这种可能需要根据国情定制的需求,建议大家还是小心使用。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。