WeChatHandler.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. // Copyright (c) .NET Foundation. All rights reserved.
  2. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  3. using Microsoft.AspNetCore.Authentication.OAuth;
  4. using Microsoft.AspNetCore.Builder;
  5. using Microsoft.AspNetCore.Http.Authentication;
  6. using Microsoft.AspNetCore.Http.Extensions;
  7. using Microsoft.Extensions.Primitives;
  8. using Newtonsoft.Json.Linq;
  9. using System;
  10. using System.Collections.Generic;
  11. using System.Net.Http;
  12. using System.Net.Http.Headers;
  13. using System.Security.Claims;
  14. using System.Text;
  15. using System.Threading.Tasks;
  16. using Microsoft.AspNetCore.Authentication;
  17. namespace Winsoft.GOV.XF.WXCore.OAuth
  18. {
  19. internal class WeChatHandler : OAuthHandler<WeChatOptions>
  20. {
  21. public WeChatHandler(HttpClient httpClient)
  22. : base(httpClient)
  23. {
  24. }
  25. protected override async Task<AuthenticateResult> HandleRemoteAuthenticateAsync()
  26. {
  27. AuthenticationProperties properties = null;
  28. var query = Request.Query;
  29. var error = query["error"];
  30. if (!StringValues.IsNullOrEmpty(error))
  31. {
  32. var failureMessage = new StringBuilder();
  33. failureMessage.Append(error);
  34. var errorDescription = query["error_description"];
  35. if (!StringValues.IsNullOrEmpty(errorDescription))
  36. {
  37. failureMessage.Append(";Description=").Append(errorDescription);
  38. }
  39. var errorUri = query["error_uri"];
  40. if (!StringValues.IsNullOrEmpty(errorUri))
  41. {
  42. failureMessage.Append(";Uri=").Append(errorUri);
  43. }
  44. return AuthenticateResult.Fail(failureMessage.ToString());
  45. }
  46. var code = query["code"];
  47. var state = query["state"];
  48. var oauthState = query["oauthstate"];
  49. properties = Options.StateDataFormat.Unprotect(oauthState);
  50. if (state != Options.StateAddition || properties == null)
  51. {
  52. return AuthenticateResult.Fail("The oauth state was missing or invalid.");
  53. }
  54. // OAuth2 10.12 CSRF
  55. if (!ValidateCorrelationId(properties))
  56. {
  57. return AuthenticateResult.Fail("Correlation failed.");
  58. }
  59. if (StringValues.IsNullOrEmpty(code))
  60. {
  61. return AuthenticateResult.Fail("Code was not found.");
  62. }
  63. //获取tokens
  64. var tokens = await ExchangeCodeAsync(code, BuildRedirectUri(Options.CallbackPath));
  65. var identity = new ClaimsIdentity(Options.ClaimsIssuer);
  66. AuthenticationTicket ticket = null;
  67. if (Options.WeChatScope == Options.InfoScope)
  68. {
  69. //获取用户信息
  70. ticket = await CreateTicketAsync(identity, properties, tokens);
  71. }
  72. else
  73. {
  74. //不获取信息,只使用openid
  75. identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, tokens.TokenType, ClaimValueTypes.String, Options.ClaimsIssuer));
  76. ticket = new AuthenticationTicket(new ClaimsPrincipal(identity), properties, Options.AuthenticationScheme);
  77. }
  78. if (ticket != null)
  79. {
  80. return AuthenticateResult.Success(ticket);
  81. }
  82. else
  83. {
  84. return AuthenticateResult.Fail("Failed to retrieve user information from remote server.");
  85. }
  86. }
  87. /// <summary>
  88. /// OAuth第一步,获取code
  89. /// </summary>
  90. /// <param name="properties"></param>
  91. /// <param name="redirectUri"></param>
  92. /// <returns></returns>
  93. protected override string BuildChallengeUrl(AuthenticationProperties properties, string redirectUri)
  94. {
  95. //加密OAuth状态
  96. var oauthstate = Options.StateDataFormat.Protect(properties);
  97. //
  98. redirectUri = $"{redirectUri}?{nameof(oauthstate)}={oauthstate}";
  99. var queryBuilder = new QueryBuilder()
  100. {
  101. { "appid", Options.ClientId },
  102. { "redirect_uri", redirectUri },
  103. { "response_type", "code" },
  104. { "scope", Options.WeChatScope },
  105. { "state", Options.StateAddition },
  106. };
  107. return Options.AuthorizationEndpoint + queryBuilder.ToString();
  108. }
  109. /// <summary>
  110. /// OAuth第二步,获取token
  111. /// </summary>
  112. /// <param name="code"></param>
  113. /// <param name="redirectUri"></param>
  114. /// <returns></returns>
  115. protected override async Task<OAuthTokenResponse> ExchangeCodeAsync(string code, string redirectUri)
  116. {
  117. var tokenRequestParameters = new Dictionary<string, string>()
  118. {
  119. { "appid", Options.ClientId },
  120. { "secret", Options.ClientSecret },
  121. { "code", code },
  122. { "grant_type", "authorization_code" },
  123. };
  124. var requestContent = new FormUrlEncodedContent(tokenRequestParameters);
  125. var requestMessage = new HttpRequestMessage(HttpMethod.Post, Options.TokenEndpoint);
  126. requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
  127. requestMessage.Content = requestContent;
  128. var response = await Backchannel.SendAsync(requestMessage, Context.RequestAborted);
  129. if (response.IsSuccessStatusCode)
  130. {
  131. var payload = JObject.Parse(await response.Content.ReadAsStringAsync());
  132. string ErrCode = payload.Value<string>("errcode");
  133. string ErrMsg = payload.Value<string>("errmsg");
  134. if (!string.IsNullOrEmpty(ErrCode) | !string.IsNullOrEmpty(ErrMsg))
  135. {
  136. return OAuthTokenResponse.Failed(new Exception($"ErrCode:{ErrCode},ErrMsg:{ErrMsg}"));
  137. }
  138. var tokens = OAuthTokenResponse.Success(payload);
  139. //借用TokenType属性保存openid
  140. tokens.TokenType = payload.Value<string>("openid");
  141. return tokens;
  142. }
  143. else
  144. {
  145. var error = "OAuth token endpoint failure";
  146. return OAuthTokenResponse.Failed(new Exception(error));
  147. }
  148. }
  149. /// <summary>
  150. /// OAuth第四步,获取用户信息
  151. /// </summary>
  152. /// <param name="identity"></param>
  153. /// <param name="properties"></param>
  154. /// <param name="tokens"></param>
  155. /// <returns></returns>
  156. protected override async Task<AuthenticationTicket> CreateTicketAsync(ClaimsIdentity identity, AuthenticationProperties properties, OAuthTokenResponse tokens)
  157. {
  158. var queryBuilder = new QueryBuilder()
  159. {
  160. { "access_token", tokens.AccessToken },
  161. { "openid", tokens.TokenType },//在第二步中,openid被存入TokenType属性
  162. { "lang", "zh_CN" }
  163. };
  164. var infoRequest = Options.UserInformationEndpoint + queryBuilder.ToString();
  165. var response = await Backchannel.GetAsync(infoRequest, Context.RequestAborted);
  166. if (!response.IsSuccessStatusCode)
  167. {
  168. throw new HttpRequestException($"Failed to retrieve WeChat user information ({response.StatusCode}) Please check if the authentication information is correct and the corresponding WeChat Graph API is enabled.");
  169. }
  170. var user = JObject.Parse(await response.Content.ReadAsStringAsync());
  171. var ticket = new AuthenticationTicket(new ClaimsPrincipal(identity), properties, Options.AuthenticationScheme);
  172. var context = new OAuthCreatingTicketContext(ticket, Context, Options, Backchannel, tokens, user);
  173. var identifier = user.Value<string>("openid");
  174. if (!string.IsNullOrEmpty(identifier))
  175. {
  176. identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, identifier, ClaimValueTypes.String, Options.ClaimsIssuer));
  177. }
  178. var nickname = user.Value<string>("nickname");
  179. if (!string.IsNullOrEmpty(nickname))
  180. {
  181. identity.AddClaim(new Claim(ClaimTypes.Name, nickname, ClaimValueTypes.String, Options.ClaimsIssuer));
  182. }
  183. var sex = user.Value<string>("sex");
  184. if (!string.IsNullOrEmpty(sex))
  185. {
  186. identity.AddClaim(new Claim("urn:WeChat:sex", sex, ClaimValueTypes.String, Options.ClaimsIssuer));
  187. }
  188. var country = user.Value<string>("country");
  189. if (!string.IsNullOrEmpty(country))
  190. {
  191. identity.AddClaim(new Claim(ClaimTypes.Country, country, ClaimValueTypes.String, Options.ClaimsIssuer));
  192. }
  193. var province = user.Value<string>("province");
  194. if (!string.IsNullOrEmpty(province))
  195. {
  196. identity.AddClaim(new Claim(ClaimTypes.StateOrProvince, province, ClaimValueTypes.String, Options.ClaimsIssuer));
  197. }
  198. var city = user.Value<string>("city");
  199. if (!string.IsNullOrEmpty(city))
  200. {
  201. identity.AddClaim(new Claim("urn:WeChat:city", city, ClaimValueTypes.String, Options.ClaimsIssuer));
  202. }
  203. var headimgurl = user.Value<string>("headimgurl");
  204. if (!string.IsNullOrEmpty(headimgurl))
  205. {
  206. identity.AddClaim(new Claim("urn:WeChat:headimgurl", headimgurl, ClaimValueTypes.String, Options.ClaimsIssuer));
  207. }
  208. var unionid = user.Value<string>("unionid");
  209. if (!string.IsNullOrEmpty(unionid))
  210. {
  211. identity.AddClaim(new Claim("urn:WeChat:unionid", unionid, ClaimValueTypes.String, Options.ClaimsIssuer));
  212. }
  213. await Options.Events.CreatingTicket(context);
  214. return context.Ticket;
  215. }
  216. }
  217. }