Open edX各种登录方式探索
文章目录
缘起
对异构系统的整合是我的兴趣之一,Open edX的开放式设计使它很容易与其他系统整合,其中包括用户系统的整合
前前后后折腾了edx的各种登录和注册机制,在此理一下。前几天对python-social-auth做了探索,也一并做下笔记
主要内容包括:
- CAS
- OAuth2
- OAuth2 client
- OAuth2 server
- 更改login机制,同时支持username/email登录
- QQ/微信登录
- 移动端跳转
CAS
关于CAS,我此前有写过一篇文章为什么CAS应该成为你的LMS的一部分
上边文章介绍了CAS的使用场景和它的原理,也给出了一些参考资料,对CAS不熟悉的小伙伴可以参考
看到这里就假设你基本弄懂CAS的原理啦,那我们直接讨论如何将CAS和edx对接
@MT说edx内置的cas client坑比较多,所以我试着改造了django-cas,这是改造后的:wwj718/django-cas,按照项目主页的引导,你就可以直接在Open edX里使用cas啦
具体的实现可以参考我的commit,代码很短,就几行
OAuth2
关于OAuth2的学习可以参考我的笔记:OAuth学习笔记
如果你对此熟悉 ,我们继续前进
CAS登录中,有一个中心,这个中心是CAS server,这种登录方式往往是集中式的。而OAuth2可以用于分布式登录的场景,尽管它的用途不只于此(还包括访问受限的资源)。
你一定使用过这种登录方式,许多网站都支持支持QQ/微信/google/facebook登录
这便使用了OAuth2
OAuth2 client
当我们访问网站A(比如我们的edx实例)时,使用了QQ登录,那么有以下事件发生
- 用户访问A网站,选择QQ登录,网站将用户导向qq认证服务器。
- 用户登录QQ,并选择是否给予网站A授权。
- 若用户给予授权,QQ认证服务器将用户导向A网站事先指定的”重定向URI”(redirection URI),同时附上一个授权码。
- A网站收到授权码,附上早先的”重定向URI”,向QQ认证服务器申请令牌。这一步是在A网站的后台的服务器上完成的,对用户不可见。
- QQ认证服务器核对了授权码和重定向URI,确认无误后,向A网站发送访问令牌(access token)和更新令牌(refresh token)。
之后A网站在后端携带用户的access token,可能拿到用户资料了。至此A网站的后端可以知道用户A是否是A 网站的合法用户,对于采用QQ登录网站A而言,已经足够了。
如果你熟悉OAuth2,你会发现这就是使用最广泛的授权码模式
需要注意的是,本地用户系统如何与用户QQ资料挂钩,诸如展示用户名或者用户头像,这些不是oauth2该做的,即便走通了oauth2的流程只意味着,该用户在QQ认证服务器那边是合法用户,同时本地后台也可以拿到用户资料(QQ昵称/头像),可是如何把用户昵称和头像与网站A本地系统整合,好比username和昵称挂钩,或是与id挂钩,这需要在网站A注册系统里手写逻辑,一般在上边的最后一步做,注册完成之后,把用户重定向到dashboard。可以参考edx中既有的google/facebook/linkedin
你也可以参考这个案例:Logging-into-Django-w–Twitter,尽管这个案例和edx无关,但它基本把流程说清了
值得一提的事,edx整合外部登录到系统里的方式是采用用户绑定而不是直接注册,所以会导致以下问题
如果你使用python-social-auth,即便你好不容易,折腾半天,感觉已经没问题了,腾讯那边还会说点击QQ登录按钮提示登录失败或出现错误信息(无跳转、提示失败、出现错误信息)
,于是不让你审核通过,原因是edx的third_party_auth
并不会自动将oauth2登录通过的用户注册到edx里,而是要求你使用edx既有用户绑定一下,之后才可以使用qq登录。
这样一来腾讯那边认为你并没有登录成功,所以没法审核通过
以上是Open edx使用OAuth2 client登录qq的场景
最后需要郑重提醒的是网站基本信息里回调地址得是http:xxx/auth/complete/qq/
OAuth2 server
lms的 OAuth2 server 用的是django-oauth2-provider
下边我们讨论Open edX作为OAuth2 server的场景,这时Open edx相当于我们上边提到的QQ认证服务器的角色,此时B网站就可以使用Open edx的用户登录他们的网站,诸如insights就是这样做的,insights本身是个独立完整的网站,为了与lms/cms整合在一起,采用了OAuth2来关联用户,这时候lms就扮演了OAuth2 server的角色,只要你拥有lms的用户,就可以直接登录insights,用户的感觉是只有一个用户系统。
整个认证的流程和上边基本相同,具体的操作可以参考edX Analytics Installation
不同的地方主要是OAuth2 server(lms)和OAuth2 client(insights)都是自己的,所以OAuth2 client是受信任的client,这是一种客户端模式
,比前头提到的授权码模式来得简单,关于这些模式的对比,可以参考我的这篇文章:OAuth学习笔记
关于客户端模式的代码,参考enable Open edX REST APIs(work with mobile),通过客户端模式,我们可以轻易了解用户和密码的正确性
|
|
产生受信任客户端的核心就是我们能控制OAuth2 server,在其中执行:
sudo /edx/bin/python.edxapp /edx/bin/manage.edxapp lms --setting=aws create_oauth2_client http://insight:18110 http://insight:18110/complete/edx-oidc/ confidential --client_name insights --client_id YOUR_OAUTH2_KEY --client_secret secret --trusted
其中http://insight:18110/complete/edx-oidc/
是回调地址,这一步往往是两个平台用户关联的核心所在,有兴趣的同学可以直接翻源码,出于篇幅限制,在此不详述。这个url,来自python-social-auth
这里给我们的一个启示是:如果你想拓展edx,所做的拓展并不需要再界面上与edx整合(内嵌的整合可以用djangoapp/xblock),而又希望两者能看起来像一个系统(用户打通),那么采用insights的这种架构就很好,实际上,open edx的整个项目就是由若干服务组成的,edx本身的就够就是由若干异构系统拼成的,这个今天人气很高的微服务
有异曲同工之处
顺便再提一下,insights中有许多地方需要lms的数据,诸如题目统计里需要统计各类题目的正误情况,所以我们需要题目的信息,而这些信息insights里是不包括的,实际上这也是通过rest接口完成的,认证机制也是OAuth2,接口在这里Course Structure API
回到OAuth2 server的话题,lms作为OAuth2 server,我们首先需要启动它,通过在lms.env.json
里设置(FEATURES)
1 2 3 4 5 6 7 8 9 10 11 12 |
:::text ... "FEATURES: { ... "ENABLE_OAUTH2_PROVIDER": true, "OAUTH_ENFORCE_SECURE": false, ... } "JWT_ISSUER": "http://LMS/oauth2", "OAUTH_OIDC_ISSUER": "http://LMS/oauth2", "OAUTH_ENFORCE_SECURE": false, #这个放在FEATURES里? ... |
而在insight里,确保/edx/etc/insights.yml
(如果跑脚本的时候设置正确,这些会自动生成)
1 2 3 4 5 |
:::text CMS_COURSE_SHORTCUT_BASE_URL: http://LMS/course COURSE_API_URL: http://LMS/api/course_structure/v0/ MODULE_PREVIEW_URL: http://LMS/xblock SOCIAL_AUTH_EDX_OIDC_URL_ROOT: http://LMS/oauth2 |
外部登录细节
我们从insights开始,我们把insights看做一个oauth2 client,登录入口为/accounts/login/
1 2 3 4 |
:::text url(r'^accounts/login/$', RedirectView.as_view(url=reverse_lazy('social:begin', args=['edx-oidc']), permanent=False, query_string=True), name='login'), |
通信的过程是标准的oauth2登录方式,具体的请求地址可以参考:auth-backends
其中EdXOpenIdConnect是关键所在
相关请求url为:
- AUTHORIZATION_URL : http://LMS/oauth2/authorize/ //使用get获取code,有时效性
- ACCESS_TOKEN_URL : http://LMS/oauth2/access_token/ //使用post 携带参数获得access_token
- USER_INFO_URL : http://LMS/oauth2/user_info/
我们可以试试手动获取用户数据
|
|
以上采用的是受信任客户端的模式
这一块的单步调试非常错综复杂,是应为oauth2的url部分写得奇蠢无比。
|
|
官方采取了url覆盖的方法,而不是明确指出,以至于你如果不深入源码丛林就找不到url对应的view,而由于这些是外部库,所以ack也很不方便
我们只好求助一些工具: sudo /edx/bin/python.edxapp /edx/app/edxapp/edx-platform/manage.py lms show_urls --settings devstack | grep user_info
1 2 3 |
:::text /api/mobile/v0.5/my_user_info rest_framework.decorators.my_user_info /oauth2/user_info/ oauth2_provider.views.UserInfoView oauth2:user_info |
从中我们找到了user_info对于的方法,它来自edx_oauth2_provider,不要问题为何知道。。
如果你要手动处理oauth2,试试:requests-oauthlib,分步调试的话,可以看这个:examples/google,不过需要https
更多的调试细节,比如各个参数的含义,那么你需要了解oauth2协议本身,那样可以从http层面调试,否则会很艰难,还是尽量使用oauth2 client吧
如果你对过程参数感兴趣可以参考使用Authorization_Code获取Access_Token
对原理说的最清楚的为使用 OAuth 2.0 访问豆瓣 API
当携带code请求access_token是,使用http –form提交,例子如:
1
|
http --form http://LMS/oauth2/access_token client_id=key client_secret=secret code=xxx grant_type=authorization_code redirect_uri=http://domain_test |
至于如何拿到code
1
|
http --form http://LMS/oauth2/access_token client_id=key client_secret=secret code=xxx grant_type=authorization_code redirect_uri=http://domain_test |
```
,然后你将得到access token,其中有id_token参数,这个参数是jwt加密后的,需要解密
更改login机制
我们可能处于各种原因需要修改login机制,诸如想同时支持email和username,诸如不想验证用户的有效性,诸如允许某些用户携带秘钥直接登录
在此分享一下同时支持email和username的登录方式的思路。更改登录逻辑当然是核心
登录逻辑在common/djangoapps/student/views.py
中的login_user函数,更改这部分倒是容易,麻烦反而在前端
前端并不写在template里,而是写在openedx/core/djangoapps/user_api/views.py
中,找到LoginSessionView
更改即可
QQ/微信登录
关于QQ登录大体流程在OAuth2 client中已经说了,如果你在这部分困难重重,除了QQ本身的坑之外(对此我们无能为力),你最好确保自己熟悉OAuth2,这样方便单步调试
,只要你走通了一个OAuth2认证,其他的基本没有难度
调试的细节建议参考开发攻略_Client-side
客户端注册:connect.qq.com
移动端跳转
如果你不采用彼岸准的oauth2的方式来认证用户,而是由于各种历史原因想走捷径(最好不要这么干!很脏乱,不好维护)。好吧你还是不听,那我建议你采用jwt的方式来携带用户信息,应为有加密,起码它至少是安全的
关于jwt你可以参考我的这篇文章:JWT学习笔记
总结
关于用户系统,我最喜欢的一种设计是,它应该是可插拔式的
工具
文章作者 种瓜
上次更新 2016-04-12