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(混淆码)是否已经被使用。
那么这样攻击者就没法自已生成 签名,或者偷你的签名来使用了。
关于开发文档
参考: