PageRenderTime 44ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 0ms

/src/NetDimension.Weibo/OAuth.cs

https://github.com/pwg17/NetDimension.Weibo
C# | 454 lines | 332 code | 45 blank | 77 comment | 32 complexity | 7cbccfdc66a69602d20ef6b5e569e6dc MD5 | raw file
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.IO;
  5. using System.Net;
  6. using System.Security.Cryptography;
  7. using System.Text;
  8. using System.Text.RegularExpressions;
  9. using Newtonsoft.Json;
  10. using Newtonsoft.Json.Linq;
  11. #if NET40
  12. using Codeplex.Data;
  13. #endif
  14. #if !NET20
  15. using System.Linq;
  16. #endif
  17. namespace NetDimension.Weibo {
  18. /// <summary>
  19. /// OAuth2.0授权类
  20. /// </summary>
  21. public class OAuth {
  22. private const string AUTHORIZE_URL = "https://api.weibo.com/oauth2/authorize";
  23. private const string ACCESS_TOKEN_URL = "https://api.weibo.com/oauth2/access_token";
  24. /// <summary>
  25. /// 实例化OAuth类(用于授权)
  26. /// </summary>
  27. /// <param name="appKey">AppKey</param>
  28. /// <param name="appSecret">AppSecret</param>
  29. /// <param name="callbackUrl">指定在新浪开发平台后台中所绑定的回调地址</param>
  30. public OAuth(string appKey, string appSecret, string callbackUrl = null) {
  31. this.AppKey = appKey;
  32. this.AppSecret = appSecret;
  33. this.AccessToken = string.Empty;
  34. this.CallbackUrl = callbackUrl;
  35. }
  36. /// <summary>
  37. /// 实例化OAuth类(用于实例化操作类)
  38. /// </summary>
  39. /// <param name="appKey">AppKey</param>
  40. /// <param name="appSecret">AppSecret</param>
  41. /// <param name="accessToken">已经获取的AccessToken,若Token没有过期即可通过操作类Client调用接口</param>
  42. /// <param name="refreshToken">目前还不知道这个参数会不会开放,保留</param>
  43. public OAuth(string appKey, string appSecret, string accessToken, string refreshToken = null) {
  44. this.AppKey = appKey;
  45. this.AppSecret = appSecret;
  46. this.AccessToken = accessToken;
  47. this.RefreshToken = refreshToken ?? string.Empty;
  48. }
  49. /// <summary>
  50. /// 获取App Key
  51. /// </summary>
  52. public string AppKey { get; internal set; }
  53. /// <summary>
  54. /// 获取App Secret
  55. /// </summary>
  56. public string AppSecret { get; internal set; }
  57. /// <summary>
  58. /// 获取Access Token
  59. /// </summary>
  60. public string AccessToken { get; internal set; }
  61. /// <summary>
  62. /// 获取或设置回调地址
  63. /// </summary>
  64. public string CallbackUrl { get; set; }
  65. /// <summary>
  66. /// Refresh Token 似乎目前没用
  67. /// </summary>
  68. public string RefreshToken { get; internal set; }
  69. internal string Request(string url, RequestMethod method = RequestMethod.Get, params WeiboParameter[] parameters) {
  70. string rawUrl = string.Empty;
  71. var uri = new UriBuilder(url);
  72. string result = string.Empty;
  73. bool multi = false;
  74. #if !NET20
  75. multi = parameters.Count(p => p.IsBinaryData) > 0;
  76. #else
  77. foreach (WeiboParameter item in parameters) {
  78. if (item.IsBinaryData) {
  79. multi = true;
  80. break;
  81. }
  82. }
  83. #endif
  84. switch (method) {
  85. case RequestMethod.Get: {
  86. uri.Query = Utility.BuildQueryString(parameters);
  87. }
  88. break;
  89. case RequestMethod.Post: {
  90. if (!multi) {
  91. uri.Query = Utility.BuildQueryString(parameters);
  92. }
  93. }
  94. break;
  95. }
  96. if (string.IsNullOrEmpty(this.AccessToken)) {
  97. if (uri.Query.Length == 0) {
  98. uri.Query = "source=" + this.AppKey;
  99. }
  100. else {
  101. uri.Query += "&source=" + this.AppKey;
  102. }
  103. }
  104. var http = WebRequest.Create(uri.Uri) as HttpWebRequest;
  105. http.ServicePoint.Expect100Continue = false;
  106. http.UserAgent = "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0)";
  107. if (!string.IsNullOrEmpty(this.AccessToken)) {
  108. http.Headers["Authorization"] = string.Format("OAuth2 {0}", this.AccessToken);
  109. }
  110. switch (method) {
  111. case RequestMethod.Get: {
  112. http.Method = "GET";
  113. }
  114. break;
  115. case RequestMethod.Post: {
  116. http.Method = "POST";
  117. if (multi) {
  118. string boundary = Utility.GetBoundary();
  119. http.ContentType = string.Format("multipart/form-data; boundary={0}", boundary);
  120. http.AllowWriteStreamBuffering = true;
  121. using (Stream request = http.GetRequestStream()) {
  122. try {
  123. byte[] raw = Utility.BuildPostData(boundary, parameters);
  124. request.Write(raw, 0, raw.Length);
  125. }
  126. finally {
  127. request.Close();
  128. }
  129. }
  130. }
  131. else {
  132. http.ContentType = "application/x-www-form-urlencoded";
  133. using (var request = new StreamWriter(http.GetRequestStream())) {
  134. try {
  135. request.Write(Utility.BuildQueryString(parameters));
  136. }
  137. finally {
  138. request.Close();
  139. }
  140. }
  141. }
  142. }
  143. break;
  144. }
  145. try {
  146. using (WebResponse response = http.GetResponse()) {
  147. using (var reader = new StreamReader(response.GetResponseStream())) {
  148. try {
  149. result = reader.ReadToEnd();
  150. }
  151. catch (WeiboException) {
  152. throw;
  153. }
  154. finally {
  155. reader.Close();
  156. }
  157. }
  158. response.Close();
  159. }
  160. }
  161. catch (WebException webEx) {
  162. if (webEx.Response != null) {
  163. using (var reader = new StreamReader(webEx.Response.GetResponseStream())) {
  164. string errorInfo = reader.ReadToEnd();
  165. #if DEBUG
  166. Debug.WriteLine(errorInfo);
  167. #endif
  168. var error = JsonConvert.DeserializeObject<Error>(errorInfo);
  169. reader.Close();
  170. throw new WeiboException(string.Format("{0}", error.Code), error.Message, error.Request);
  171. }
  172. }
  173. else {
  174. throw new WeiboException(webEx.Message);
  175. }
  176. }
  177. catch {
  178. throw;
  179. }
  180. return result;
  181. }
  182. /// <summary>
  183. /// OAuth2的authorize接口
  184. /// </summary>
  185. /// <param name="response">返回类型,支持code、token,默认值为code。</param>
  186. /// <param name="state">用于保持请求和回调的状态,在回调时,会在Query Parameter中回传该参数。 </param>
  187. /// <param name="display">授权页面的终端类型,取值见下面的说明。
  188. /// default 默认的授权页面,适用于web浏览器。
  189. /// mobile 移动终端的授权页面,适用于支持html5的手机。
  190. /// popup 弹窗类型的授权页,适用于web浏览器小窗口。
  191. /// wap1.2 wap1.2的授权页面。
  192. /// wap2.0 wap2.0的授权页面。
  193. /// js 微博JS-SDK专用授权页面,弹窗类型,返回结果为JSONP回掉函数。
  194. /// apponweibo 默认的站内应用授权页,授权后不返回access_token,只刷新站内应用父框架。
  195. /// </param>
  196. /// <returns></returns>
  197. public string GetAuthorizeURL(ResponseType response = ResponseType.Code, string state = null, DisplayType display = DisplayType.Default) {
  198. var config = new Dictionary<string, string> {
  199. {"client_id", this.AppKey},
  200. {"redirect_uri", this.CallbackUrl},
  201. {"response_type", response.ToString().ToLower()},
  202. {"state", state ?? string.Empty},
  203. {"display", display.ToString().ToLower()},
  204. };
  205. var builder = new UriBuilder(AUTHORIZE_URL);
  206. builder.Query = Utility.BuildQueryString(config);
  207. return builder.ToString();
  208. }
  209. /// <summary>
  210. /// 判断AccessToken有效性
  211. /// </summary>
  212. /// <returns></returns>
  213. public TokenResult VerifierAccessToken() {
  214. try {
  215. string json = this.Request("https://api.weibo.com/2/account/get_uid.json", RequestMethod.Get);
  216. }
  217. catch (WeiboException ex) {
  218. switch (ex.ErrorCode) {
  219. case "21314":
  220. return TokenResult.TokenUsed;
  221. case "21315":
  222. return TokenResult.TokenExpired;
  223. case "21316":
  224. return TokenResult.TokenRevoked;
  225. case "21317":
  226. return TokenResult.TokenRejected;
  227. default:
  228. return TokenResult.Other;
  229. }
  230. }
  231. return TokenResult.Success;
  232. }
  233. public bool ClientLogin(string passport, string password) {
  234. return this.ClientLogin(passport, password, null);
  235. }
  236. /// <summary>
  237. /// 使用模拟方式进行登录并获得AccessToken
  238. /// </summary>
  239. /// <param name="passport">微博账号</param>
  240. /// <param name="password">微博密码</param>
  241. /// <returns></returns>
  242. public bool ClientLogin(string passport, string password, AccessToken token) {
  243. bool result = false;
  244. #if !NET20
  245. ServicePointManager.ServerCertificateValidationCallback = (sender, certificate,chain,sslPolicyErrors) =>
  246. {
  247. return true;
  248. };
  249. #else
  250. ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
  251. #endif
  252. var MyCookieContainer = new CookieContainer();
  253. var http = WebRequest.Create(AUTHORIZE_URL) as HttpWebRequest;
  254. http.Referer = this.GetAuthorizeURL();
  255. http.Method = "POST";
  256. http.ContentType = "application/x-www-form-urlencoded";
  257. http.AllowAutoRedirect = true;
  258. http.KeepAlive = true;
  259. http.CookieContainer = MyCookieContainer;
  260. string postBody = string.Format("action=submit&withOfficalFlag=0&ticket=&isLoginSina=&response_type=token&regCallback=&redirect_uri={0}&client_id={1}&state=&from=&userId={2}&passwd={3}&display=js", Uri.EscapeDataString(string.IsNullOrEmpty(this.CallbackUrl) ? "" : this.CallbackUrl), Uri.EscapeDataString(this.AppKey), Uri.EscapeDataString(passport), Uri.EscapeDataString(password));
  261. byte[] postData = Encoding.Default.GetBytes(postBody);
  262. http.ContentLength = postData.Length;
  263. using (Stream request = http.GetRequestStream()) {
  264. try {
  265. request.Write(postData, 0, postData.Length);
  266. }
  267. catch {
  268. throw;
  269. }
  270. finally {
  271. request.Close();
  272. }
  273. }
  274. string code = string.Empty;
  275. try {
  276. using (var response = http.GetResponse() as HttpWebResponse) {
  277. if (response != null) {
  278. using (var reader = new StreamReader(response.GetResponseStream())) {
  279. try {
  280. string html = reader.ReadToEnd();
  281. string pattern1 = @"\{""access_token"":""(?<token>.{0,32})"",""remind_in"":""(?<remind>\d+)"",""expires_in"":(?<expires>\d+),""uid"":""(?<uid>\d+)""\}";
  282. string pattern2 = @"\{""access_token"":""(?<token>.{0,32})"",""remind_in"":""(?<remind>\d+)"",""expires_in"":(?<expires>\d+),""refresh_token"":""(?<refreshtoken>.{0,32})"",""uid"":""(?<uid>\d+)""\}";
  283. if (!string.IsNullOrEmpty(html) && (Regex.IsMatch(html, pattern1) || Regex.IsMatch(html, pattern2))) {
  284. Match group = Regex.IsMatch(html, "refresh_token") ? Regex.Match(html, pattern2) : Regex.Match(html, pattern1);
  285. this.AccessToken = group.Groups["token"].Value;
  286. if (token != null) {
  287. token.ExpiresIn = Convert.ToInt32(group.Groups["expires"].Value);
  288. token.Token = group.Groups["token"].Value;
  289. token.UID = group.Groups["uid"].Value;
  290. }
  291. result = true;
  292. }
  293. }
  294. catch {
  295. }
  296. finally {
  297. reader.Close();
  298. }
  299. }
  300. }
  301. response.Close();
  302. }
  303. }
  304. catch (WebException) {
  305. throw;
  306. }
  307. return result;
  308. }
  309. #region AccessToken获取的方法集合
  310. /// <summary>
  311. /// 使用code方式获取AccessToken
  312. /// </summary>
  313. /// <param name="code">Code</param>
  314. /// <returns></returns>
  315. public AccessToken GetAccessTokenByAuthorizationCode(string code) {
  316. return this.GetAccessToken(GrantType.AuthorizationCode, new Dictionary<string, string> {
  317. {"code", code},
  318. {"redirect_uri", this.CallbackUrl}
  319. });
  320. }
  321. /// <summary>
  322. /// 使用password方式获取AccessToken
  323. /// </summary>
  324. /// <param name="passport">账号</param>
  325. /// <param name="password">密码</param>
  326. /// <returns></returns>
  327. public AccessToken GetAccessTokenByPassword(string passport, string password) {
  328. return this.GetAccessToken(GrantType.Password, new Dictionary<string, string> {
  329. {"username", passport},
  330. {"password", password}
  331. });
  332. }
  333. /// <summary>
  334. /// 使用token方式获取AccessToken
  335. /// </summary>
  336. /// <param name="refreshToken">refresh token,目前还不知道从哪里获取这个token,未开放</param>
  337. /// <returns></returns>
  338. public AccessToken GetAccessTokenByRefreshToken(string refreshToken) {
  339. return this.GetAccessToken(GrantType.RefreshToken, new Dictionary<string, string> {
  340. {"refresh_token", refreshToken}
  341. });
  342. }
  343. /// <summary>
  344. /// 站内应用使用SignedRequest获取AccessToken
  345. /// </summary>
  346. /// <param name="signedRequest">SignedRequest</param>
  347. /// <returns></returns>
  348. public AccessToken GetAccessTokenBySignedRequest(string signedRequest) {
  349. string[] parameters = signedRequest.Split('.');
  350. if (parameters.Length < 2) {
  351. throw new Exception("SignedRequest格式错误。");
  352. }
  353. string encodedSig = parameters[0];
  354. string payload = parameters[1];
  355. var sha256 = new HMACSHA256(Encoding.UTF8.GetBytes(this.AppSecret));
  356. string expectedSig = Convert.ToBase64String(sha256.ComputeHash(Encoding.UTF8.GetBytes(payload)));
  357. sha256.Clear();
  358. encodedSig = parameters[0].Length % 4 == 0 ? parameters[0] : parameters[0].PadRight(parameters[0].Length + (4 - parameters[0].Length % 4), '=').Replace("-", "+").Replace("_", "/");
  359. payload = parameters[1].Length % 4 == 0 ? parameters[1] : parameters[1].PadRight(parameters[1].Length + (4 - parameters[1].Length % 4), '=').Replace("-", "+").Replace("_", "/");
  360. if (encodedSig != expectedSig) {
  361. throw new WeiboException("SignedRequest签名验证失败。");
  362. }
  363. JObject result = JObject.Parse(Encoding.UTF8.GetString(Convert.FromBase64String(payload)));
  364. if (result["oauth_token"] == null) {
  365. return null; //throw new WeiboException("没有获取到授权信息,请先进行授权。");
  366. }
  367. var token = new AccessToken();
  368. this.AccessToken = token.Token = result["oauth_token"].ToString();
  369. token.UID = result["user_id"].ToString();
  370. token.ExpiresIn = Convert.ToInt32(result["expires"].ToString());
  371. return token;
  372. }
  373. internal AccessToken GetAccessToken(GrantType type, Dictionary<string, string> parameters) {
  374. var config = new List<WeiboParameter> {
  375. new WeiboParameter {Name = "client_id", Value = this.AppKey},
  376. new WeiboParameter {Name = "client_secret", Value = this.AppSecret}
  377. };
  378. switch (type) {
  379. case GrantType.AuthorizationCode: {
  380. config.Add(new WeiboParameter {Name = "grant_type", Value = "authorization_code"});
  381. config.Add(new WeiboParameter {Name = "code", Value = parameters["code"]});
  382. config.Add(new WeiboParameter {Name = "redirect_uri", Value = parameters["redirect_uri"]});
  383. }
  384. break;
  385. case GrantType.Password: {
  386. config.Add(new WeiboParameter {Name = "grant_type", Value = "password"});
  387. config.Add(new WeiboParameter {Name = "username", Value = parameters["username"]});
  388. config.Add(new WeiboParameter {Name = "password", Value = parameters["password"]});
  389. }
  390. break;
  391. case GrantType.RefreshToken: {
  392. config.Add(new WeiboParameter {Name = "grant_type", Value = "refresh_token"});
  393. config.Add(new WeiboParameter {Name = "refresh_token", Value = parameters["refresh_token"]});
  394. }
  395. break;
  396. }
  397. string response = this.Request(ACCESS_TOKEN_URL, RequestMethod.Post, config.ToArray());
  398. if (!string.IsNullOrEmpty(response)) {
  399. var token = JsonConvert.DeserializeObject<AccessToken>(response);
  400. this.AccessToken = token.Token;
  401. return token;
  402. }
  403. else {
  404. return null;
  405. }
  406. }
  407. #endregion
  408. }
  409. }