惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Know Your Adversary
Know Your Adversary
C
CERT Recently Published Vulnerability Notes
V
Vulnerabilities – Threatpost
N
News | PayPal Newsroom
O
OpenAI News
A
About on SuperTechFans
月光博客
月光博客
Martin Fowler
Martin Fowler
L
LangChain Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
aimingoo的专栏
aimingoo的专栏
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
V2EX - 技术
V2EX - 技术
博客园 - 司徒正美
大猫的无限游戏
大猫的无限游戏
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
人人都是产品经理
人人都是产品经理
T
Tor Project blog
D
DataBreaches.Net
Cloudbric
Cloudbric
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
云风的 BLOG
云风的 BLOG
F
Full Disclosure
S
SegmentFault 最新的问题
Vercel News
Vercel News
T
Tailwind CSS Blog
Schneier on Security
Schneier on Security
宝玉的分享
宝玉的分享
M
MIT News - Artificial intelligence
博客园 - 【当耐特】
P
Privacy International News Feed
美团技术团队
S
Secure Thoughts
P
Privacy & Cybersecurity Law Blog
Google DeepMind News
Google DeepMind News
F
Fortinet All Blogs
Scott Helme
Scott Helme
Forbes - Security
Forbes - Security
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Recent Announcements
Recent Announcements
AWS News Blog
AWS News Blog
Stack Overflow Blog
Stack Overflow Blog
S
Security Affairs
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
The GitHub Blog
The GitHub Blog
T
Tenable Blog
Cyberwarzone
Cyberwarzone
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
J
Java Code Geeks
T
Threatpost

夜行人

回家路上 第一期的直播演示项目 震动检测器 正能量 在线参观CodeLab Neverland 发布 CodeLab Adapter 3.3.1 DynamicTable 之 纸糊方向盘 CodeLab DynamicTable: 一个可实施的技术方案 CodeLab Insight 发布 Alpha 版 情人节 Home Assistant 周报 && IoT 周报 (02) Joplin: 关注隐私的 Evernote 开源替代软件 浏览器的未来与 Web 传感器 Home Assistant 周报 && IoT 周报 (01) 百宝箱(01) 论自由 介绍 WebThings Home Assistant 周报 && iot 周报 (00) 百宝箱(00) 毛姆读书心得 传世之作 周末徒步 CodeLab Adapter ❤️ Jupyter/Python 航班 躲雨 夏令营途中 [译]思想--作为一种技术 The future of coding 美国之行 三门问题的程序模拟 从Python转向Pharo https://blog.just4fun.site/post/iot/iot-open-source-projects/ Python异步编程笔记 https://blog.just4fun.site/post/iot/iot-open-source-hardware-community/ 万物积木化开发者社区 CodeLab ❤️ Blender Scratch3技术分析之云变量 API(第7篇) [译]对管道(Pipes)的偏爱 [译]提出正确的问题比得到正确答案更重要 蓝牙设备与Scratch3.0 创建你的第一个Scratch3.0 Extension Scratch3技术分析之项目内部数据(第6篇) Scratch3技术分析之社区 API(第5篇) Scratch3技术分析之User API(第4篇) Scratch3技术分析之项目主页API(第3篇) Scratch3技术分析之静态资源API(第2篇) Scratch3.0、micro:bit与Windows7 https://blog.just4fun.site/post/iot/zerynth-vs-micropython/ 核聚变、方所与半宅空间 可视化编程为何是个糟糕的主意 codelab.club周末聚会 关于codelab.club '下一件大事'是一个房间 Hungry Robot - Eat everything 编程作为一种思考方式 今日简史 史蒂夫·乔布斯传 罗素自选文集 https://blog.just4fun.site/post/edx/tianjin-scratch-ai/ https://blog.just4fun.site/post/edx/richie-cms-openedx/ 徒步武功山 WebUSB与micro:bit 积木化编程与3D场景 夜宿武功山顶 scratch3-adapter接入优必选Alpha系列机器人 https://blog.just4fun.site/post/edx/video-migration-note/ scratch3-adapter重构笔记 https://blog.just4fun.site/post/edx/edx-community-members/ 两种硬件编程风格的比较 使用micro:bit自制PPT翻页笔 柏拉图对话集 scratch3.0 + micro:bit 七月电影放映计划 非营利组织的管理 Screenly--用树莓派让任何屏幕变为可编程的数字标牌 以最佳实践开始你的Django项目 micro:bit与事件驱动 为Scratch3.0设计的插件系统(上篇) OCR应用一例 近两年读过的一些好书 blockly开发之使用python驱动浏览器中的turtle(2) 牛顿新传 文学理论入门 逻辑的引擎 人生的意义 blockly开发之生成并运行js代码(1) blockly开发之hello world(0) micro:bit使用笔记 神器之Termux https://blog.just4fun.site/post/iot/micropython-notes/ Cozmo what is this Scratch的前世今生 下段旅程 我行在远方 爆裂 途中杂记 https://blog.just4fun.site/post/edx/open-edx-startup/ cozmo系列之入门 - 有性格且可编程的机器人 PaperWeekly开发笔记 创业二三事
Open edX各种登录方式探索
2016-04-12 · via 夜行人

缘起

对异构系统的整合是我的兴趣之一,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),通过客户端模式,我们可以轻易了解用户和密码的正确性

1
2
3
4
5
6
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)

 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/

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 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部分写得奇蠢无比。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
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

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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

,然后你将得到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)