2

Open edX各种登录方式探索

 2 years ago
source link: http://wwj718.github.io/post/edx/open-edx-login-explore/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Open edX各种登录方式探索

2016-04-12

对异构系统的整合是我的兴趣之一,Open edX的开放式设计使它很容易与其他系统整合,其中包括用户系统的整合

前前后后折腾了edx的各种登录和注册机制,在此理一下。前几天对python-social-auth做了探索,也一并做下笔记

主要内容包括:

  • OAuth2
    • OAuth2 client
    • OAuth2 server
  • 更改login机制,同时支持username/email登录
  • QQ/微信登录
  • 移动端跳转

关于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),通过客户端模式,我们可以轻易了解用户和密码的正确性

import requests
import requests.auth
client_auth = requests.auth.HTTPBasicAuth('dc107056a5335b3a7c74', '4e3f1fad6e0583fc80d78541f2ca6cfad8a93bed')
post_data = {"grant_type": "password", "username": "wwj", "password": "wwj"}
response = requests.post("https://127.0.0.1/oauth2/access_token", auth=client_auth, data=post_data, verify=False)
response.json()

产生受信任客户端的核心就是我们能控制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)

:::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(如果跑脚本的时候设置正确,这些会自动生成)

:::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/

:::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/

我们可以试试手动获取用户数据

# get access_token
import requests
import requests.auth
client_auth = requests.auth.HTTPBasicAuth('key', 'secret')
post_data = {"grant_type": "password", "username": "wwj", "password": "wwj"}
response = requests.post("http://LMS/oauth2/access_token", auth=client_auth, data=post_data, verify=False)
response.json()

import requests
headers = {"Authorization": "bearer xxx", "User-Agent": "ChangeMeClient/0.1 by YourUsername"}
response = requests.get("http:/LMS/api/mobile/v0.5/my_user_info", headers=headers)
response.json()

以上采用的是受信任客户端的模式


这一块的单步调试非常错综复杂,是应为oauth2的url部分写得奇蠢无比。

if settings.FEATURES.get('ENABLE_OAUTH2_PROVIDER'):
    urlpatterns += (
        # These URLs dispatch to django-oauth-toolkit or django-oauth2-provider as appropriate.
        # Developers should use these routes, to maintain compatibility for existing client code
        url(r'^oauth2/', include('lms.djangoapps.oauth_dispatch.urls')),
        # These URLs contain the django-oauth2-provider default behavior.  It exists to provide
        # URLs for django-oauth2-provider to call using reverse() with the oauth2 namespace, and
        # also to maintain support for views that have not yet been wrapped in dispatch views.
        url(r'^oauth2/', include('edx_oauth2_provider.urls', namespace='oauth2')), 
        # The /_o/ prefix exists to provide a target for code in django-oauth-toolkit that
        # uses reverse() with the 'oauth2_provider' namespace.  Developers should not access these
        # views directly, but should rather use the wrapped views at /oauth2/
        url(r'^_o/', include('oauth2_provider.urls', namespace='oauth2_provider')),
    )

官方采取了url覆盖的方法,而不是明确指出,以至于你如果不深入源码丛林就找不到url对应的view,而由于这些是外部库,所以ack也很不方便

我们只好求助一些工具: sudo /edx/bin/python.edxapp /edx/app/edxapp/edx-platform/manage.py lms show_urls --settings devstack | grep user_info

:::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提交,例子如:

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

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](http://wiki.connect.qq.com/%E5%BC%80%E5%8F%91%E6%94%BB%E7%95%A5_client-side)

客户端注册:[connect.qq.com](http://connect.qq.com/)

# 移动端跳转
如果你不采用彼岸准的oauth2的方式来认证用户,而是由于各种历史原因想走捷径(最好不要这么干!很脏乱,不好维护)。好吧你还是不听,那我建议你采用jwt的方式来携带用户信息,应为有加密,起码它至少是安全的

关于jwt你可以参考我的这篇文章:[JWT学习笔记](http://blog.just4fun.site/jwt-note.html)

# 总结
关于用户系统,我最喜欢的一种设计是,它应该是可插拔式的

# 工具 
*  [url转码](http://tool.chinaz.com/tools/urlencode.aspx)

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK