OAuth2的實際應用中,最常見的就是“授權碼模式”了。
微博是這種模式,微信也是這種模式。
總結來說,就是簡單的二步:
- 1.獲取code
- 2.根據code,去獲取access_token
以微博為例(http://open.weibo.com/wiki/%E6%8E%88%E6%9D%83%E6%9C%BA%E5%88%B6%E8%AF%B4%E6%98%8E):
假設我們開發一個網站(稱為client;相對應的,微博就是server了),網站允許用戶以微博賬號登錄,且需要讀取用戶的微博信息(賬號、評論等等)。
顯然,這過程中需要用戶授權。
第一步:
- https://api.weibo.com/oauth2/authorize?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=YOUR_REGISTERED_REDIRECT_URI
第二步:
- https://api.weibo.com/oauth2/access_token?client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET&grant_type=authorization_code&redirect_uri=YOUR_REGISTERED_REDIRECT_URI&code=CODE
這其中有幾個問題:
1.獲取code時,傳遞的二個變量(clent_id,redirect_url),沒有一個是跟用戶有關的,那微博怎么知道我們請求的是哪個用戶的授權呢?
這個問題現在看來很可笑,但剛開始確實是蒙圈了。
其實,在第一步的URI發出后,是要求用戶跳轉到server端登錄的。這很好理解,如果用戶不登錄,那你怎么知道是哪個用戶呢,他又怎么給你授權呢?
此外,可以看到,第一步的URI,放在我們網站頁面的任何位置都可以;它也不要求傳遞跟session相關的任何信息。
這個URI對應的頁面是微博開發的,跟我們網站沒關系。
事實上,這個URI可以直接拷貝到瀏覽器里發起請求;這個時候,是瀏覽器跟微博的一個交互,我們的網站是一無所知的。
那什么時候我們得知用戶給我們授權了呢?
第一步URI的請求,瀏覽器得到的是一個http的重定向響應,這個重定向指向的就是我們的網站;此時瀏覽器向我們的網站發起請求:
類似於:
https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz
站在我們網站后台程序的角度來看,我們是“忽然”(在此這前,它一無所知)收到一個code了,而這個code,是跟微博的一個用戶一一對應的,我們拿這個code向微博發起請求,就能獲得用戶的授權,獲得access_token,再發起另一請求,就可以獲取用戶信息了。
2.為什么在第一個URI請求發出后,微博的302重定向響應中,沒有直接返回access_token,而是只返回一個code呢?
其實最初我的疑問更可笑:我想,為什么微博不直接返回access_token給我們網站呢?
這個問題的答案是,發起第一步的URI請求的,是瀏覽器!還記得前面我說的“我們的網站是一無所知”嗎?此時微博是與瀏覽器在通信,而不是我們的網站,當然不能把參數傳返回到我們網站了。
微博只能通過返回一個302響應,讓瀏覽器重新向我們網站發起請求,把code傳到我們網站來。
回到第二個問題。
該博客下有人是這樣回答的:
- 為什么授權碼模式需要這個授權碼 當然是為了安全性 首先在OAuth體系中access_token是作為訪問獲取資源的唯一憑據 如果在AS授權完成之后 直接通過重定向傳回access_token 那么HTTP 302不是安全的 Attacker有可能會獲取到access_token 但是如果只返回Authorization code 就算別人獲得了也沒什么卵用 因為Authorization code不能獲取到資源 在client向AS請求access_token的過程中 是通過HTTPS來保證安全的 而且獲得access_token是需要client secret與Authorization code一起的 Attacker知道了Authorization code但並不知道client secret 同樣也不能獲得到access_token 所以client與AS是有責任保護好client secret的
- 獲得了access_token之后 向RS發起請求 RS其實會與AS交互 來校驗access_token 所以你想直接偽造一個access_token 那也是不ok的
- 突然想到漏說了一塊 關於為什么不直接用HTTPS重定向回client
- 是因為不是所有client server都支持HTTPS~所以為了通用性 和安全性 才衍生出來這么一個Auth code
- 但是AS肯定是實現HTTPS的 所以在client向AS提起request 是木有問題的~
我自己的理解,是這樣的:
直接返回access_token有可能被黑客攔截到,而拿到access_token就可以獲得用戶信息了,非常不安全。
那最容易想到的是,微博通過https返回access_token啊,https會對access_token加密,那不就可以了嗎?
為什么這個方法不可行呢?
因為有可能我們的網站不支持https!站在微博的角度來看,千千萬萬的第三方網站,你要求每個網站都支持https,是不太現實的。
stackoverflow上的一個回答(http://stackoverflow.com/questions/13387698/why-is-there-an-authorization-code-flow-in-oauth2-when-implicit-flow-works-s)說這是“一個巨大的痛苦”:“a huge pain”。
那為什么返回code再去獲取access_token就安全了呢?
因為這時候再去獲取access_token,就是https請求了,發起方為我們的網站,接收方為微博,而微博作為OAuth server,肯定提供https。
但是,另一個問題來了,黑客拿到了code,它也向微博發起請求去獲取access_token是不是就可以了呢?
也是不可以的。
因為向微博請求access_token時,還要帶上我們網站的“密碼”(client secret)。
這個密碼,是我們網站在微博開放平台上注冊時獲得的(同時會得到一個client_id,這個值在第一步的URI里用到了)。
這一點在該博客沒有提出來,最容易讓人困惑。
3.為什么簡化模式(implicit grant type)是可行的?
這個在stackoverflow有回答,前面也提到過:
http://stackoverflow.com/questions/13387698/why-is-there-an-authorization-code-flow-in-oauth2-when-implicit-flow-works-s
這就涉及到一個知識點:
瀏覽器不會把URL當中的“hash fragment”發給服務器。hash fragment是只留在瀏覽器的地址欄的,它可以被網頁的js讀取到:window.location.hash。
既然hash fragment沒有發給服務器,那即使黑客攔截到請求,也獲取不到它。
什么是hash fragment?就是URL當中#號后面的那一部分。
博客中的示例:
- HTTP/1.1 302 Found
- Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA&state=xyz&token_type=example&expires_in=3600
可以得知,“#access_token=2YotnFZFEjr1zCsicMWpAA”這一部分是留在瀏覽器這邊的。
當瀏覽器接收到到該響應時,向http://example.com/cb發起請求,而后者的響應頁面當中,應當有讀取hash fragment的代碼。
這樣,client就拿到了access_token了。
解決了這三個問題,我本想到實際場景中抓包看看的,但遺憾的是,不管是蘑菇街還是聚美優品,這些允許以微博賬號登錄的網站,都只看到獲取code那一步,沒有看到獲取access_token那一步。
或許改天有空可以自己在微博開放平台上注冊一個應用來驗證一下。
不過網上有人驗證過微博的:
https://segmentfault.com/a/1190000000666685
此外還有一些關於微信OAuth2的實際例子:
http://www.cnblogs.com/txw1958/p/weixin71-oauth20.html
http://zeusjava.com/2015/04/22/wechat-get-userinfo/
都講得比較詳細。
zz:https://bylijinnan.iteye.com/blog/2277548