OAuth有什麼用?爲何要使用OAuth?php
twitter或豆瓣用戶必定會發現,有時候,在別的網站,點登陸後轉到 twitter登陸,以後轉回原網站,你會發現你已經登陸此網站了,這種網站就是這個效果。其實這都是拜 OAuth所賜。html
OAuth(開放受權)是一個開放標準,容許用戶讓第三方應用訪問該用戶在某一網站上存儲的私密的資源(如照片,視頻,聯繫人列表),而無需將用戶名和密碼提供給第三方應用。程序員
OAuth 協議爲用戶資源的受權提供了一個安全的、開放而又簡易的標準。與以往的受權方式不一樣之處是OAUTH的受權不會使第三方觸及到用戶的賬號信息(如用戶名與密 碼),即第三方無需使用用戶的用戶名與密碼就能夠申請得到該用戶資源的受權,所以OAUTH是安全的。同時,任何第三方均可以使用OAUTH認證服務,任 何服務提供商均可以實現自身的OAUTH認證服務,於是OAUTH是開放的。業界提供了OAUTH的多種實現如PHP,JavaScript,Java,Ruby等各類語言開發包,大大節約了程序員的時間,於是OAUTH是簡易的。目前互聯網不少服務如Open API,不少大頭公司如Google,Yahoo,Microsoft等都提供了OAUTH認證服務,這些都足以說明OAUTH標準逐漸成爲開放資源受權的標準。web
認證和受權過程ajax
在認證和受權的過程當中涉及的三方包括:api
注意到用戶提交密碼是在第四步 而第三方Consumer從頭到尾沒有得到帳號信息 瀏覽器
Oauth的流程 官方的說明圖以下:安全
使用OAuth進行認證和受權的過程以下所示:服務器
可獲得:Consumer Key,Consumer Secret。twitter申請oauth的話,在 setting - connection - developer 裏面申請。 同時給出三個訪問網址:cookie
Consumer須要先取得 請求另牌(Request Token)。網址爲上面的 request_token_url,參數爲:
這樣 Consumer就取得了 請求另牌(包括另牌名 oauth_token,另牌密鑰 oauth_token_secret。
網址爲authorize_url?oauth_token=請求另牌名
那麼會自動轉回上面的 oauth_callback 裏定義的網址。同時加上 oauth_token (就是請求另牌),及oauth_verifier(驗證碼)。
五,如今總能夠開始請求資源了吧?
NO。如今還須要再向 服務提供商 請求 訪問另牌(Access Token)。網址爲上面的access_token_url,參數爲:
這樣就能夠取得 訪問另牌(包括Access Token 及 Access Token Secret)。這個就是須要保存在 Consumer上面的信息(沒有你的真實用戶名,密碼,安全吧!)
Consumer就能夠做爲用戶的身份訪問 服務提供商上被保護的資源了。提交的參數以下:
OAuth2.0
OAuth2.0和OAuth1.0的區別仍是在於簡化了認證過程,不須要從未受權的Request Token轉化到受權Request Token,而是利用app key經過用戶受權生成access token可是,與1.0的不一樣之處是access token有自身的有效期,且不一樣平臺、不一樣級別的程序有着不一樣的有效期,在程序開發中必定記得判斷access token是否過時,對於過時以後的處理方法主要是利用access token和refresh token從新生成access token或者從新利用app key向服務器發送請求生成access token。因爲這個問題,與OAuth1.0基本一致不同,各個平臺OAuth2.0作了不同的選擇。
OAuth2.0服務支持如下獲取Access Token的方式:
a. Authorization Code:Web Server Flow,適用於全部有Server端配合的應用。
b. Implicit Grant:User-Agent Flow,適用於全部無Server端配合的應用。
由於demo是無服務器的程式,因此咱們採用Implicit Grant:User-Agent Flow的獲取方式。
示意圖(來自騰訊微博開發文檔)
獲取Access Token
爲了獲取Access Token,應用須要將用戶瀏覽器(或手機/桌面應用中的瀏覽器組件)到OAuth2.0受權服務的「http://xxxxxxxxx/authorize」地址上,並帶上如下參數:
參數名 | 必選 | 介紹 |
client_id | true | 申請組件時得到的API Key |
response_type | true | 此值固定爲「token」 |
redirect_uri | true | 受權後要回調的URI,即接受code的URI。對於無Web Server的應用,其值能夠是「oob」。 |
scope | false | 以空格分隔的權限列表,若不傳遞此參數,表明請求默認的basic權限。如需調用擴展權限,必需傳遞此參數 |
state | false | 用於保持請求和回調的狀態,受權服務器在回調時(重定向用戶瀏覽器到「redirect_uri」時),會在Query Parameter中原樣回傳該參數 |
display | false | 登陸和受權頁面的展示樣式,默認爲「page」。手機訪問時,此參數無效 |
client | false | 是否爲手機訪問。手機訪問:client=1;不是手機,無需次參數 |
若用戶登陸並接受受權,受權服務將重定向用戶瀏覽器到「redirect_uri」,並在Fragment中追加以下參數:
參數名 | 介紹 |
access_token | 要獲取的Access Token |
expires_in | Access Token的有效期,以秒爲單位 |
refresh_token | 用於刷新Access Token 的 Refresh Token,一些平臺不返回這個參數,須要程序員進行判斷處理 |
scope | Access Token最終的訪問範圍,即用戶實際授予的權限列表 |
state | 若是請求獲取Access Token時帶有state參數,則將該參數原樣返回 |
一、首先是到登陸地址 https://login.facebook.com/login.php?login_attempt=1 嘛,存儲好 cookies 而且從頁面解析出一個 lsd 的參數,而後再次向此地址提交登陸參數,包括 charset_test=%E2%82%AC%2C%C2%B4%2C%E2%82%AC%2C%C2%B4%2C%E6%B0%B4%2C%D0%94%2C%D0%8四、return_session=0、legacy_return=一、display=、session_key_only=0、trynum=一、email、pass、persistent=一、login=%E7%99%BB%E5%BD%9五、lsd。
二、在登陸成功以後保存 cookies 轉向 http://www.facebook.com/home.php ,而後今後頁面解析出 profile_id、 composer_id、post_form_id 、fb_dtsg 4個參數出來。
三、最後一步向 http://www.facebook.com/ajax/updatestatus.php?__a=1 提交狀態信息,須要包括如下參數 action=HOME_UPDATE、home_tab_id=一、profile_id、status、target_id=0、app_id、privacy_data[value]=80、privacy_data[friends]=0、privacy_data[list_anon]=0、privacy_data[list_x_anon]=0、composer_id、composer_id、hey_kid_im_a_composer=true、display_context=home、post_form_id、fb_dtsg、lsd、_log_display_context=home、ajax_log=一、post_form_id_source=AsyncRequest 。
如下的程序段呢,若是出問題還請參照一下之前的幾篇文章,好比使用代理地址,好比訪問 https 須要注意的地方等。
private void update_facebook(string user, string password, string message) { string service = "Facebook"; this.BeginInvoke(new UpdateStatusDelegate(StateInfo), new object[] { "正在將消息發佈到 " + service }); CookieContainer cc = new CookieContainer(); string lsd = ""; Uri uri = new Uri("https://login.facebook.com/login.php?login_attempt=1"); HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri); request.UserAgent = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)"; request.ContentType = "application/x-www-form-urlencoded"; request.Method = "GET"; request.CookieContainer = cc; if (!string.IsNullOrEmpty(proxyserver)) { request.Proxy = new WebProxy(proxyserver); ServicePointManager.ServerCertificateValidationCallback = new System.Net.Security.RemoteCertificateValidationCallback(CheckValidationResult); } try { using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) { foreach (Cookie cookie in response.Cookies) { cc.Add(cookie); } Stream responseStream = response.GetResponseStream(); StreamReader reader = new StreamReader(responseStream, Encoding.GetEncoding("UTF-8")); string result = reader.ReadToEnd(); Match re = Regex.Match(result, @"name=\u0022lsd\u0022\svalue=\u0022([^\u0022]*)\u0022()"); lsd = re.Groups[1].ToString(); } } catch (Exception ex) { ErrorMessage("獲取登陸到 "+ service +" 必要參數時出現意外:\r\n"+ex.Message); return; } StringBuilder postData = new StringBuilder(); postData.Append("charset_test=%E2%82%AC%2C%C2%B4%2C%E2%82%AC%2C%C2%B4%2C%E6%B0%B4%2C%D0%94%2C%D0%84"); postData.Append("&lsd=" + lsd + "&return_session=0&legacy_return=1&display=&session_key_only=0&trynum=1"); postData.Append("&email=" + Utility.UrlEncode(user) + "&pass=" + Utility.UrlEncode(password) + "&persistent=1&login=%E7%99%BB%E5%BD%95"); byte[] bs = Encoding.UTF8.GetBytes(postData.ToString()); request = (HttpWebRequest)HttpWebRequest.Create(uri); request.UserAgent = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)"; request.ContentType = "application/x-www-form-urlencoded"; request.Method = "POST"; request.ContentLength = bs.Length; request.CookieContainer = cc; request.AllowAutoRedirect = true; if (!string.IsNullOrEmpty(proxyserver)) { request.Proxy = new WebProxy(proxyserver); } try { using (Stream requestStream = request.GetRequestStream()) { requestStream.Write(bs, 0, bs.Length); requestStream.Close(); } using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) { foreach (Cookie cookie in response.Cookies) { cc.Add(cookie); } response.Close(); } } catch (Exception ex) { ErrorMessage("登陸到 " + service + " 時出現意外:\n"+ex.Message); return; } uri = new Uri("http://www.facebook.com/home.php"); request = (HttpWebRequest)HttpWebRequest.Create(uri); request.UserAgent = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)"; request.ContentType = "application/x-www-form-urlencoded"; request.Method = "GET"; request.CookieContainer = cc; if (!string.IsNullOrEmpty(proxyserver)) { request.Proxy = new WebProxy(proxyserver); } string profile_id = "", composer_id = "", post_form_id = "", fb_dtsg = ""; try { using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) { foreach (Cookie cookie in response.Cookies) { cc.Add(cookie); } Stream responseStream = response.GetResponseStream(); StreamReader reader = new StreamReader(responseStream, Encoding.GetEncoding("UTF-8")); string result = reader.ReadToEnd(); Match re = Regex.Match(result, @"name=\u0022profile_id\u0022\svalue=\u0022([^\u0022]*)\u0022()"); profile_id = re.Groups[1].ToString(); re = Regex.Match(result, @"name=\u0022composer_id\u0022\svalue=\u0022([^\u0022]*)\u0022()"); composer_id = re.Groups[1].ToString(); re = Regex.Match(result, @"name=\u0022post_form_id\u0022\svalue=\u0022([^\u0022]*)\u0022()"); post_form_id = re.Groups[1].ToString(); re = Regex.Match(result, @"name=\u0022fb_dtsg\u0022\svalue=\u0022([^\u0022]*)\u0022()"); fb_dtsg = re.Groups[1].ToString(); response.Close(); } } catch (Exception ex) { ErrorMessage("重定向facebook頁面並獲取發佈消息所需參數時出現意外:\n"+ex.Message); return; } postData = new StringBuilder(); postData.Append("action=HOME_UPDATE&home_tab_id=1&profile_id=" + profile_id); postData.Append("&status=" + Utility.UrlEncode(message) + "&target_id=0&app_id="); postData.Append("&privacy_data[value]=80&privacy_data[friends]=0&&&&&privacy_data[list_anon]=0&&privacy_data[list_x_anon]=0"); postData.Append("&&composer_id=" + composer_id + "&hey_kid_im_a_composer=true&display_context=home"); postData.Append("&post_form_id=" + post_form_id + "&fb_dtsg=" + fb_dtsg+"&lsd="+lsd); postData.Append("&_log_display_context=home&ajax_log=1&post_form_id_source=AsyncRequest"); bs = Encoding.UTF8.GetBytes(postData.ToString()); uri = new Uri("http://www.facebook.com/ajax/updatestatus.php?__a=1"); request = (HttpWebRequest)HttpWebRequest.Create(uri); request.UserAgent = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)"; request.ContentType = "application/x-www-form-urlencoded"; request.Method = "POST"; request.CookieContainer = cc; request.ContentLength = bs.Length; if (!string.IsNullOrEmpty(proxyserver)) { request.Proxy = new WebProxy(proxyserver); } try { using (Stream requestStream = request.GetRequestStream()) { requestStream.Write(bs, 0, bs.Length); requestStream.Close(); } } catch (Exception ex) { ErrorMessage("發佈消息到 facebook 時出現意外:\n"+ex.Message); return; } }
Twitter 更新消息時注意加入 postBody 參數就能夠叻,更多細節能夠對照 」簡版 OAuthr 認證 for C#「
/// <summary>、 /// OAuth 認證更新twitter消息 /// </summary> /// <param name="consumer_key">應用的consumer_key</param> /// <param name="consumer_secret">應用的consumer_secret</param> /// <param name="oauth_token">應用的access_key</param> /// <param name="oauth_token_secret">應用的access_secret</param> /// <param name="message">發送的消息</param> /// <param name="request_path">請求的API</param> private void update_twitter( string consumer_key, string consumer_secret, string oauth_token, string oauth_token_secret, string message, string request_path) { string service = "推特"; System.Net.ServicePointManager.Expect100Continue = false; this.BeginInvoke(new UpdateStatusDelegate(StateInfo), new object[] { "正在將消息發佈到 " + service }); string postData = "status=" + Utility.UrlEncode(message); byte[] bs = Encoding.UTF8.GetBytes(postData); Dictionary<string, string> param = new Dictionary<string, string>(); param = OAuth.RequestParams( consumer_key, consumer_secret, oauth_token, oauth_token_secret, request_path, "POST", postData, null); string head_string = OAuth.Dict2Header(param); try { HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(request_path); request.ContentType = "application/x-www-form-urlencoded"; request.Method = "POST"; request.ContentLength = bs.Length; if (!string.IsNullOrEmpty(proxyserver)) { request.Proxy = new WebProxy(proxyserver); ServicePointManager.ServerCertificateValidationCallback = new System.Net.Security.RemoteCertificateValidationCallback(CheckValidationResult); } request.Headers.Add("Authorization", "OAuth realm=\"http://t.yunmengze.net\"," + head_string); using (Stream reqStream = request.GetRequestStream()) { reqStream.Write(bs, 0, bs.Length); reqStream.Close(); } } catch (Exception e) { ErrorMessage("將消息發佈到 " + service + " 時出現意外,建議暫時取消這一服務的同步:\r\n" + e.Message, 1); return; } }
若是使用代理,加上一段
public bool CheckValidationResult(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors) { //直接確認,不然打不開 return true; }
OAuth安全機制是如何實現的?
OAuth 使用的簽名加密方法有 HMAC-SHA1,RSA-SHA1 (能夠自定義)。拿 HMAC-SHA1 來講吧,HMAC-SHA1這種加密碼方法,可使用 私鑰 來加密 要在網絡上傳輸的數據,而這個私鑰只有 Consumer及服務提供商知道,試圖攻擊的人即便獲得傳輸在網絡上的字符串,沒有 私鑰 也是白搭。
私鑰是:consumer secret&token secret (哈兩個密碼加一塊兒)
要加密的字符串是:除 oauth_signature 外的其它要傳輸的數據。按參數名字符排列,若是同樣,則按 內容排。如:domain=kejibo.com&oauth_consumer_key=XYZ& word=welcome………………….
前面提的加密裏面都是固定的字符串,那麼攻擊者豈不是直接能夠偷取使用嗎?
不,oauth_timestamp,oauth_nonce。這兩個是變化的。並且服務器會驗證一個 nonce(混淆碼)是否已經被使用。
那麼這樣攻擊者就沒法自已生成 簽名,或者偷你的簽名來使用了。
關於開發文檔
參考: