client api

當時處理這部分的動機是將edx與微信對接html

若是你在處理與edx API相關的工做,這篇文章可能對你也有幫助。比如你在編譯edx移動端(android和iOS), 這部分工做應該也是最主要的工做之一java

思路

咱們首先簡要作一下任務陳述:容許edx用戶經過微信公衆平臺訪問edX,登陸以及請求相關的數據android

這裏假設讀者們已經基本瞭解了OAuth2,包括它的一些基本概念和通訊流程,若是還不瞭解,請先閱讀OAuth2相關的材料。nginx

在咱們的任務中,咱們先識別出OAuth中的參與實體,RO(resource owner),RS (resource server)和Client,至於AS(authorization server)在edx中和RS能夠認爲一體。git

很顯然咱們的任務中,edx平臺做爲RS,而edx user是RO,而咱們本身寫的微信公衆號後臺即是Client。github

因爲微信後端和平臺擁有者是相同的,因此我就不採用redirect的方式了。而假設Client是受信任的。django

那麼通訊的流程是這樣的,edx user在微信給微信公衆號中給Client發送帳號和密碼,然後Client攜帶用戶帳號和密碼去換取受權令牌(Access Token),且存下受權令牌,如此一來,概念上,用戶在微信中便已經保持登陸edX的狀態了。json

然後Client根據用戶請求,攜帶Access Token去服務器請求資源返回給微信用戶。後端

這裏不該當混淆的是,使用微信帳戶登陸edx,和在微信中以edx user身份訪問edx,是兩個徹底不一樣的過程,使用微信帳戶登陸edx本質上是個第三方社交帳號登陸edx的問題,RS是微信,而edx user在微信中訪問edx,RS是edX。api

好了,思路基本清晰了。

先前的經驗

以前寫過一篇博客:讓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-providerdjango-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_VIDEO_ABSTRACTION_LAYER_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

{"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": "wwj@example.com", "date_joined": "2015-05-13T09:42:45Z"}

若是你使用httpie(推薦),那麼返回的內容將以更易於閱讀的形式(縮進高亮),返回給你.以後咱們都只要httpie

http http://example.com/api/user/v1/accounts/wwj "Authorization: Bearer 1a17079824f66bfa5116bd8780b5a119e603a79c" (其實是header參數)

{
    "bio": null,
    "country": null,
    "date_joined": "2015-05-13T09:42:45Z",
    "email": "wwj@qq.com",
    "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() 

獲得

{u'bio': None,
 u'country': None,
 u'date_joined': u'2015-05-13T09:42:45Z',
 u'email': u'wwj@qq.com',
 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支持

生成證書

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

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結尾里加上

  # 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() 

``

ok

{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裏:
ssl on;
ssl_certificate /etc/nginx/conf/your-ssl-unified.crt;
ssl_certificate_key /etc/nginx/conf/your-ssl.key;
  • sudo killall -HUP nginx
相關文章
相關標籤/搜索