

enable Open edX REST APIs(work with mobile)
source link: http://wwj718.github.io/post/edx/edx-api/
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.

enable Open edX REST APIs(work with mobile)
当时处理这部分的动机是将edx与微信对接
如果你在处理与edx API相关的工作,这篇文章可能对你也有帮助。好比你在编译edx移动端(android和iOS), 这部分工作应该也是最主要的工作之一
##思路 我们首先简要做一下任务陈述:允许edx用户通过微信公众平台访问edX,登录以及请求相关的数据
这里假设读者们已经基本了解了OAuth2,包括它的一些基本概念和通信流程,如果还不了解,请先阅读OAuth2相关的材料。
在我们的任务中,我们先识别出OAuth中的参与实体,RO(resource owner),RS (resource server)和Client,至于AS(authorization server)在edx中和RS可以认为一体。
很显然我们的任务中,edx平台作为RS,而edx user是RO,而我们自己写的微信公众号后台便是Client。
由于微信后端和平台拥有者是相同的,所以我就不采用redirect的方式了。而假设Client是受信任的。
那么通信的流程是这样的,edx user在微信给微信公众号中给Client发送账号和密码,而后Client携带用户账号和密码去换取授权令牌(Access Token),且存下授权令牌,如此一来,概念上,用户在微信中便已经保持登录edX的状态了。
而后Client根据用户请求,携带Access Token去服务器请求资源返回给微信用户。
这里不应当混淆的是,使用微信账户登录edx
,和在微信中以edx user身份访问edx
,是两个完全不同的过程,使用微信账户登录edx
本质上是个第三方社交账号登录edx的问题,RS是微信,而edx user在微信中访问edx,RS是edX。
好了,思路基本清晰了。
##先前的经验 之前写过一篇博客:让edx为手机端提供接口
本打算按照之前的经验,却发现,采用TokenAuthentication的解决方案除了侵入性太强,不够优雅之外,安全性也得不到保证
EdX API Authentication中有一句话,
OAuth 2.0 is an open standard used by many systems that require secure user authentication
我开始以为,secure只是个建议,稍后我们会发现,这是个强制要求。
无论是OAuth2Authentication, SessionAuthentication还是TokenAuthentication,本质都是个认证问题,而认证过程在django中间件里实现,对关注业务逻辑的开发者而言是透明的,而edx的api使用的统一是OAuth2Authentication和SessionAuthentication。
可选的路线只有一条,开始折腾OAuth2.
##目标定位 经过一番跟踪和分析,我们发现了edx/edx-oauth2-provider和django-oauth2-provider与OAuth关系最大
而他们的关系是edx/edx-oauth2-provider依赖于edx/django-oauth2-provider
而edx/django-oauth2-providerfork自caffeinehit/django-oauth2-provider
caffeinehit/django-oauth2-provider的文档对我们很有助益,
##实验
定位到这两个关键库,其实接下来的工作就轻松多了。
首先做些试探性的实验。
先去/edx/app/edxapp/lms.env.json
,在FEATURES里加上"ENABLE_OAUTH2_PROVIDER": true,
以及"ENABLE_MOBILE_REST_API":true,
,而后去admin里获取一个受信任的Client和Access Token,对应的地址分别是是/admin/oauth2_provider/trustedclient/
和/admin/oauth2/accesstoken/add/
,过期时间(Expires)可以设得远些,使其不易生效,你也通过设置OAUTH_ID_TOKEN_EXPIRATION
来控制失效时间,这个数值衡量的是用户两次登录的时间间隔,好比你要求用户每七天需要登录一次。
那么激动人心的时刻来啦,我们开始请求接口
curl -k -H "Authorization: Bearer Your_Access_Token” http://example.com/api/user/v1/accounts/wwj
:::text
{"username": "wwj", "bio": null, "requires_parental_consent": true, "name": "wwj", "country": null, "is_active": true, "profile_image": {"image_url_full": "http://example.com/static/images/default-theme/default-profile_500.de2c6854f1eb.png", "image_url_large": "http://example.com/static/images/default-theme/default-profile_120.33ad4f755071.png", "image_url_medium": "http://example.com/static/images/default-theme/default-profile_50.5fb006f96a15.png", "image_url_small": "http://example.com/static/images/default-theme/default-profile_30.ae6a9ca9b390.png", "has_image": false}, "year_of_birth": null, "level_of_education": null, "goals": null, "language_proficiencies": [], "gender": null, "mailing_address": null, "email": "[email protected]", "date_joined": "2015-05-13T09:42:45Z"}
如果你使用httpie(推荐),那么返回的内容将以更易于阅读的形式(缩进高亮),返回给你.之后我们都只要httpie
http http://example.com/api/user/v1/accounts/wwj "Authorization: Bearer 1a17079824f66bfa5116bd8780b5a119e603a79c"
(实际上是header参数)
:::text
{
"bio": null,
"country": null,
"date_joined": "2015-05-13T09:42:45Z",
"email": "[email protected]",
"gender": null,
"goals": null,
"is_active": true,
"language_proficiencies": [],
"level_of_education": null,
"mailing_address": null,
"name": "wwj",
"profile_image": {
"has_image": false,
"image_url_full": "http://example.com/static/images/default-theme/default-profile_500.de2c6854f1eb.png",
"image_url_large": "http://example.com/static/images/default-theme/default-profile_120.33ad4f755071.png",
"image_url_medium": "http://example.com/static/images/default-theme/default-profile_50.5fb006f96a15.png",
"image_url_small": "http://example.com/static/images/default-theme/default-profile_30.ae6a9ca9b390.png"
},
"requires_parental_consent": true,
"username": "wwj",
"year_of_birth": null
}
再演示一个使用requests的做法
import requests
headers = {"Authorization": "bearer 1a17079824f66bfa5116bd8780b5a119e603a79c", "User-Agent": "ChangeMeClient/0.1 by YourUsername"}
response = requests.get("http://127.0.0.1/api/user/v1/accounts/wwj", headers=headers)
response.json()
:::text
{u'bio': None,
u'country': None,
u'date_joined': u'2015-05-13T09:42:45Z',
u'email': u'[email protected]',
u'gender': None,
u'goals': None,
u'is_active': True,
u'language_proficiencies': [],
u'level_of_education': None,
u'mailing_address': None,
u'name': u'wwj',
u'profile_image': {u'has_image': False,
u'image_url_full': u'http://127.0.0.1/static/images/default-theme/default-profile_500.de2c6854f1eb.png',
u'image_url_large': u'http://127.0.0.1/static/images/default-theme/default-profile_120.33ad4f755071.png',
u'image_url_medium': u'http://127.0.0.1/static/images/default-theme/default-profile_50.5fb006f96a15.png',
u'image_url_small': u'http://127.0.0.1/static/images/default-theme/default-profile_30.ae6a9ca9b390.png'},
u'requires_parental_consent': True,
u'username': u'wwj',
u'year_of_birth': None}
###下边演示请求Access Token的过程
使用requests
import requests
import requests.auth
client_auth = requests.auth.HTTPBasicAuth('dc107056a5335b3a7c74', '4e3f1fad6e0583fc80d78541f2ca6cfad8a93bed')
post_data = {"grant_type": "password", "username": "wwj", "password": "wwjtest"}
response = requests.post("http://127.0.0.1/oauth2/access_token", auth=client_auth, data=post_data)
response.json()
得到{u'error': u'invalid_request', u'error_description': u'A secure connection is required.'}
网站需要使用https,nmap查看443端口是close状态。
配置nginx。
##启用https
Remember that you should always use HTTPS for all your OAuth 2 requests otherwise you won’t be secured.
OAuth2要求使用https。所以我们为edx做https支持
###生成证书
:::text
cd /edx/app/nginx/
mkdir conf
chown -R 777 conf #好像不大好
cd conf
#创建服务器私钥,命令会让你输入一个口令
openssl genrsa -des3 -out server.key 1024
#创建签名请求的证书(CSR)
openssl req -new -key server.key -out server.csr
#在加载SSL支持的Nginx并使用上述私钥时除去必须的口令:
cp server.key server.key.org
openssl rsa -in server.key.org -out server.key
###配置nginx
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt
在/edx/app/nginx/sites-enabled
里,将lms复制为lms_https
:::text
sudo diff lms lms_https
1,3c1
< upstream lms-backend {
< server 127.0.0.1:8000 fail_timeout=0;
< }server {
---
> server {
12,13c10,13
<
< listen 80 default;
---
> listen 443;
> ssl on;
> ssl_certificate /edx/app/nginx/conf/server.crt;
> ssl_certificate_key /edx/app/nginx/conf/server.key;
在/edx/app/nginx/sites-enabled/lms
的server结尾里加上
:::text
# Forward to HTTPS if we're an HTTP request...
if ($http_x_forwarded_proto = "http") {
set $do_redirect "true";
}
# Run our actual redirect...
if ($do_redirect = "true") {
rewrite ^ https://$host$request_uri? permanent;
}
重启nginx,https方面的设置就好了,你可以访问,https://example.com 啦
##https下请求Access Token
import requests
import requests.auth
client_auth = requests.auth.HTTPBasicAuth('dc107056a5335b3a7c74', '4e3f1fad6e0583fc80d78541f2ca6cfad8a93bed')
post_data = {"grant_type": "password", "username": "wwj", "password": "wwjtest"}
response = requests.post("https://127.0.0.1/oauth2/access_token", auth=client_auth, data=post_data, verify=False)
response.json()
:::text
{u'access_token': u'e751c317435986b2a00425ed7a93a789fbcbeccd',
u'expires_in': 2591999,
u'scope': u'',
u'token_type': u'Bearer'}
#微信后端 暂不方便公开源码
#todo
- 将mobile api相关的请求全部redirect倒https
- https证书相关
#2015.07.15更新 开发群里有小伙伴提到在用android客户端去访问服务器时,会出现这样的错误。javax.net.ssl.SSLPeerUnverifiedException: No peer certificate (文后评论中也有人提到)
这是ssl证书的问题,我此前的做法是不验证。这只是绕过了问题,而没有解决它,在此正面解决它,分以下步骤:
- 申请ssl证书,我用的是免费的startssl。可参考www.startssl.com
- 将申请来的证书加入到lms_https里:
:::text
ssl on;
ssl_certificate /etc/nginx/conf/your-ssl-unified.crt;
ssl_certificate_key /etc/nginx/conf/your-ssl.key;
- sudo killall -HUP nginx
#2015.09.23更新
Cypress中的/edx/app/nginx
目录有微调,移除了/edx/app/nginx/sites-enabled
,所以我们需要把/edx/app/nginx/sites-available/
中新建的文件软链接到/etc/nginx/sites-enabled/
里
</div
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK