using JNPF.Extras.CollectiveOAuth.Cache; using JNPF.Extras.CollectiveOAuth.Config; using JNPF.Extras.CollectiveOAuth.Models; using JNPF.Extras.CollectiveOAuth.Utils; using JNPF.Extras.CollectiveOAuth.Enums; using System.Text; using System.Security.Cryptography; using Newtonsoft.Json; namespace JNPF.Extras.CollectiveOAuth.Request; public class ElemeAuthRequest : DefaultAuthRequest { public ElemeAuthRequest(ClientConfig config) : base(config, new ElemeAuthSource()) { } public ElemeAuthRequest(ClientConfig config, IAuthStateCache authStateCache) : base(config, new ElemeAuthSource(), authStateCache) { } protected override AuthToken getAccessToken(AuthCallback authCallback) { var reqParams = new Dictionary { { "client_id", config.clientId }, { "redirect_uri", config.clientSecret }, { "code", authCallback.code }, { "grant_type", "authorization_code" }, }; var reqHeaders = this.getSpecialHeader(this.getRequestId()); var response = HttpUtils.RequestFormPost(source.accessToken(), reqParams.spellParams(), reqHeaders); var accessTokenObject = response.parseObject(); this.checkResponse(accessTokenObject); var authToken = new AuthToken { accessToken = accessTokenObject.getString("access_token"), expireIn = accessTokenObject.getInt32("expires_in"), refreshToken = accessTokenObject.getString("refresh_token"), tokenType = accessTokenObject.getString("token_type"), code = authCallback.code }; return authToken; } protected override AuthUser getUserInfo(AuthToken authToken) { // 获取商户账号信息的API接口名称 String action = "eleme.user.getUser"; // 时间戳,单位秒。API服务端允许客户端请求最大时间误差为正负5分钟。 long timestamp = DateTime.Now.Ticks; // 公共参数 var metasHashMap = new Dictionary(); metasHashMap.Add("app_key", config.clientId); metasHashMap.Add("timestamp", timestamp); string signature = this.generateElemeSignature(timestamp, action, authToken.accessToken); string requestId = this.getRequestId(); var paramsMap = new Dictionary { { "nop", "1.0.0" }, { "id", requestId }, { "action", action }, { "token", authToken.accessToken }, { "metas", JsonConvert.SerializeObject(metasHashMap) }, { "params", "{}" }, { "signature", signature } }; var reqHeaders = new Dictionary { { "Content-Type", "application/json; charset=utf-8" }, { "Accept", "text/xml,text/javascript,text/html" }, { "Accept-Encoding", "gzip" }, { "User-Agent", "eleme-openapi-java-sdk"}, { "x-eleme-requestid", requestId}, { "Authorization", this.spliceBasicAuthStr()} }; var response = HttpUtils.RequestPost(source.userInfo(), JsonConvert.SerializeObject(paramsMap), reqHeaders); var resObj = response.parseObject(); // 校验请求 if (resObj.ContainsKey("name")) { throw new Exception(resObj.getString("message")); } if (resObj.ContainsKey("error") && !resObj.getString("error").IsNullOrWhiteSpace()) { throw new Exception(resObj.getJSONObject("error").getString("message")); } var userObj = resObj.getJSONObject("result"); var authUser = new AuthUser { uuid = userObj.getString("userId"), username = userObj.getString("userName"), nickname = userObj.getString("userName"), gender = AuthUserGender.UNKNOWN, token = authToken, source = source.getName(), originalUser = resObj, originalUserStr = response }; return authUser; } public override AuthResponse refresh(AuthToken oldToken) { var reqParams = new Dictionary { { "refresh_token", oldToken.refreshToken }, { "grant_type", "refresh_token" }, }; var reqHeaders = this.getSpecialHeader(this.getRequestId()); var response = HttpUtils.RequestFormPost(source.accessToken(), reqParams.spellParams(), reqHeaders); var accessTokenObject = response.parseObject(); this.checkResponse(accessTokenObject); var authToken = new AuthToken { accessToken = accessTokenObject.getString("access_token"), refreshToken = accessTokenObject.getString("refresh_token"), expireIn = accessTokenObject.getInt32("expires_in"), tokenType = accessTokenObject.getString("token_type") }; return new AuthResponse(AuthResponseStatus.SUCCESS.GetCode(), AuthResponseStatus.SUCCESS.GetDesc(), authToken); } public override string authorize(string state) { return UrlBuilder.fromBaseUrl(base.authorize(state)) .queryParam("scope", config.scope.IsNullOrWhiteSpace() ? "all" : config.scope) .build(); } private string spliceBasicAuthStr() { string encodeToString = encodeBase64($"{config.clientId}:{config.clientSecret}"); return $"Basic {encodeToString}"; } private Dictionary getSpecialHeader(string requestId) { var headers = new Dictionary { { "Content-Type", "application/x-www-form-urlencoded;charset=UTF-8" }, { "Accept", "text/xml,text/javascript,text/html" }, { "Accept-Encoding", "gzip" }, { "User-Agent", "eleme-openapi-java-sdk"}, { "x-eleme-requestid", requestId}, { "Authorization", this.spliceBasicAuthStr()} }; return headers; } private string getRequestId() { return (Guid.NewGuid().ToString() + "|" + DateTime.Now.Ticks.ToString()).ToUpper(); } /** * 校验请求结果 * * @param response 请求结果 * @return 如果请求结果正常,则返回Exception */ private void checkResponse(Dictionary dic) { if (dic.ContainsKey("error")) { throw new Exception($"{dic.getString("error_description")}"); } } ///编码 public string encodeBase64(string contentStr, string encodeType = "utf-8") { string encode = ""; byte[] bytes = Encoding.GetEncoding(encodeType).GetBytes(contentStr); try { encode = Convert.ToBase64String(bytes); } catch { encode = contentStr; } return encode; } ///解码 public string decodeBase64(string contentStr, string encodeType = "utf-8") { string decode = ""; byte[] bytes = Convert.FromBase64String(contentStr); try { decode = Encoding.GetEncoding(encodeType).GetString(bytes); } catch { decode = contentStr; } return decode; } /** * 生成饿了么请求的Signature *

* 代码copy并修改自:https://coding.net/u/napos_openapi/p/eleme-openapi-java-sdk/git/blob/master/src/main/java/eleme/openapi/sdk/utils/SignatureUtil.java * * @param appKey 平台应用的授权key * @param secret 平台应用的授权密钥 * @param timestamp 时间戳,单位秒。API服务端允许客户端请求最大时间误差为正负5分钟。 * @param action 饿了么请求的api方法 * @param token 用户授权的token * @param parameters 加密参数 * @return Signature */ public string generateElemeSignature(long timestamp, string action, string token) { Dictionary dicList = new Dictionary(); dicList.Add("app_key", config.clientId); dicList.Add("timestamp", timestamp); var signStr = dicList.Sort().spellParams(); string splice = $"{action}{token}{signStr}{config.clientSecret}"; string calculatedSignature = hashMd5String(splice); return calculatedSignature; } ///

/// 对字符串进行Md5加密,isUpper为True时返回大写,反之小写 /// /// /// public static string hashMd5String(string willMd5Str, bool isUpper = true) { //就是比string往后一直加要好的优化容器 StringBuilder sb = new StringBuilder(); #pragma warning disable SYSLIB0021 //MD5CryptoServiceProvider已过时 using (MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider()) #pragma warning disable SYSLIB0021 //MD5CryptoServiceProvider已过时 { //将输入字符串转换为字节数组并计算哈希。 byte[] data = md5.ComputeHash(Encoding.UTF8.GetBytes(willMd5Str)); //X为 十六进制 X都是大写 x都为小写 //2为 每次都是两位数 //假设有两个数10和26,正常情况十六进制显示0xA、0x1A,这样看起来不整齐,为了好看,可以指定"X2",这样显示出来就是:0x0A、0x1A。 //遍历哈希数据的每个字节 //并将每个字符串格式化为十六进制字符串。 int length = data.Length; for (int i = 0; i < length; i++) sb.Append(data[i].ToString("X2")); } if (isUpper) { return sb.ToString().ToUpper(); } else { return sb.ToString().ToLower(); } } }