C# Sign in with Apple后台接口

一、生成client_secret

安装jose-jwt,示例代码如下

  /// 
  /// 生成JWT
  /// 
  /// 

    private string CreateAppleClientSecret() 
    {
        var iat = Math.Round((DateTime.UtcNow.AddMinutes(-1) - new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds, 0);
        var exp = Math.Round((DateTime.UtcNow.AddMinutes(30) - new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds, 0);

        string keyId = "8D7EEULXX4";
        var extraHeader = new Dictionary()
        {
            { "alg", "ES256" },
            { "kid", keyId }, //key id
            { "typ", "JWT" }
        };

        var payload = new Dictionary()
        {
            { "sub" , bundleIdentifier }, //iOS App的Bundle Identifier 
            { "exp", exp },
            { "iat", iat },
            { "iss", "2K26LU5HS2" }, //team ID
            { "aud", "https://appleid.apple.com" },
            { "origin", "https://appleid.apple.com" }
        };

        var keyString = GetApplePrivateKeyText();
        CngKey privateKey = CngKey.Import(Convert.FromBase64String(keyString), CngKeyBlobFormat.Pkcs8PrivateBlob);
        string token = JWT.Encode(payload, privateKey, JwsAlgorithm.ES256, extraHeader);

        return token;
    }

    /// 
    /// 获取苹果p8文件内容
    /// 
    /// 
    private string GetApplePrivateKeyText() 
    {
        string filePath = "iOSFiles/AuthKey_976XVM7U2S.p8"; //iOS提供的p8文件,可以不用文件,直接复制文件里边的内容
        string path = Server.MapPath(filePath);
        string text = System.IO.File.ReadAllText(path);
        //去头去尾的方法:
        var lines = text.Split('\n');
        var privateKeyText = string.Join("", lines.Skip(1).Take(lines.Length - 2).Select(l => l.Trim()));
        return privateKeyText;
    }

二、发送请求

1.请求地址为 https://appleid.apple.com/auth/token;
2.method为POST;
3.ContentType为application/x-www-form-urlencoded;
4.所需参数为
①client_id : "" //iOS App的Bundle Identifier
②grant_type : "authorization_code" //固定值
③code : 苹果验证返回authorizationCode
④client_secret : CreateAppleClientSecret() //上面步骤一生成的token

示例代码

    /// 
    /// 苹果登录
    /// 
    /// iOS App的Bundle Identifier
    /// 苹果验证返回authorizationCode
    /// 苹果验证返回user
    /// 
    public string SignInWithApple(string bundleIdentifier, string authorizationCode, string appleUserId)
    {
        try
        {
            ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback((object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors) =>
            {
                return true; //总是接受  
            });
            ServicePointManager.SecurityProtocol = (SecurityProtocolType)3072;
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://appleid.apple.com/auth/token");
            request.Method = "POST";
            request.ContentType = "application/x-www-form-urlencoded";
            var datas = new Dictionary()
                    {
                        { "client_id", bundleIdentifier },
                        { "grant_type", "authorization_code"},//固定authorization_code
                        { "code", authorizationCode },//授权码,前端验证登录给予 
                        { "client_secret", CreateAppleClientSecret() } //client_secret,后面方法生成
                    };
            string postData = JsonUrlEncode(JsonConvert.SerializeObject(datas));
            byte[] buf = Encoding.Default.GetBytes(postData);
            request.ContentLength = buf.Length;
            Stream requestStream = request.GetRequestStream();
            requestStream.Write(buf, 0, buf.Length);
            requestStream.Close();
            HttpWebResponse response = (HttpWebResponse)request.GetResponse();

            Stream stream = response.GetResponseStream();
            StreamReader streamReader = new StreamReader(stream, encoding: Encoding.UTF8);
            responseString = streamReader.ReadToEnd();
            HlllAppleAuthTokenResultModel tokenResultModel = JsonConvert.DeserializeObject(responseString);
            HlllAppleJwtPlayloadModel playloadModel = DecodeAppleJwtPlayload(tokenResultModel.id_token);
            if (!playloadModel.Aud.Equals(bundleIdentifier) || !playloadModel.Sub.Equals(appleUserId))
            {
                // "信息不正确,请重试";
            }
            else //你的服务器处理逻辑,服务器数据库添加苹果账号ID验证appleUserId
            {

            }

        }
        catch (Exception ex)
        {

        }
    }

    /// 
    /// json转urlencode
    /// 
    /// 
    public static string JsonUrlEncode(string json)
    {
        Dictionary dic = JsonConvert.DeserializeObject>(json);
        StringBuilder builder = new StringBuilder();
        foreach (KeyValuePair item in dic)
        {
            builder.Append(GetFormDataContent(item, ""));
        }
        return builder.ToString().TrimEnd('&');
    }

    /// 
    /// 递归转formdata
    /// 
    /// 
    /// 
    /// 
    private static string GetFormDataContent(KeyValuePair item, string preStr)
    {
        StringBuilder builder = new StringBuilder();
        if (string.IsNullOrEmpty(item.Value?.ToString()))
        {
            builder.AppendFormat("{0}={1}", string.IsNullOrEmpty(preStr) ? item.Key : (preStr + "[" + item.Key + "]"), System.Web.HttpUtility.UrlEncode((item.Value == null ? "" : item.Value.ToString()).ToString()));
            builder.Append("&");
        }
        else
        {
            //如果是数组
            if (item.Value.GetType().Name.Equals("JArray"))
            {
                var children = JsonConvert.DeserializeObject>(item.Value.ToString());
                for (int j = 0; j < children.Count; j++)
                {
                    Dictionary childrendic = JsonConvert.DeserializeObject>(JsonConvert.SerializeObject(children[j]));
                    foreach (var row in childrendic)
                    {
                        builder.Append(GetFormDataContent(row, string.IsNullOrEmpty(preStr) ? (item.Key + "[" + j + "]") : (preStr + "[" + item.Key + "][" + j + "]")));
                    }
                }

            }
            //如果是对象
            else if (item.Value.GetType().Name.Equals("JObject"))
            {
                Dictionary children = JsonConvert.DeserializeObject>(item.Value.ToString());
                foreach (var row in children)
                {
                    builder.Append(GetFormDataContent(row, string.IsNullOrEmpty(preStr) ? item.Key : (preStr + "[" + item.Key + "]")));
                }
            }
            //字符串、数字等
            else
            {
                builder.AppendFormat("{0}={1}", string.IsNullOrEmpty(preStr) ? item.Key : (preStr + "[" + item.Key + "]"), System.Web.HttpUtility.UrlEncode((item.Value == null ? "" : item.Value.ToString()).ToString()));
                builder.Append("&");
            }
        }

        return builder.ToString();
    }

    private HlllAppleJwtPlayloadModel DecodeAppleJwtPlayload(string jwtString)
    {
        try
        {
            var code = jwtString.Split('.')[1];
            code = code.Replace('-', '+').Replace('_', '/').PadRight(4 * ((code.Length + 3) / 4), '=');
            var bytes = Convert.FromBase64String(code);
            var decode = Encoding.UTF8.GetString(bytes);
            return JsonConvert.DeserializeObject(decode);
        }
        catch (Exception e)
        {
            throw new Exception(e.Message);
        }
    }

/// 
/// 苹果接口返回数据
/// 
public class HlllAppleAuthTokenResultModel 
{
    /// 
    /// access_token
    /// 
    public string access_token { set; get; } = "";

    /// 
    /// token_type
    /// 
    public string token_type { set; get; } = "";

    /// 
    /// expires_in
    /// 
    public long expires_in { set; get; } = 0;

    /// 
    /// refresh_token
    /// 
    public string refresh_token { set; get; } = "";

    /// 
    /// id_token
    /// 
    public string id_token { set; get; } = "";

}

/// 
/// 苹果接口返回的id_token解析
/// 
public class HlllAppleJwtPlayloadModel
{
    /// 
    /// "https://appleid.apple.com"
    /// 
    [JsonProperty("iss")]
    public string Iss { get; set; }
    /// 
    /// 这个是你的app的bundle identifier
    /// 
    [JsonProperty("aud")]
    public string Aud { get; set; }
    /// 
    ///
    /// 
    [JsonProperty("exp")]
    public long Exp { get; set; }
    /// 
    ///
    /// 
    [JsonProperty("iat")]
    public long Iat { get; set; }
    /// 
    /// 用户ID
    /// 
    [JsonProperty("sub")]
    public string Sub { get; set; }
    /// 
    ///
    /// 
    [JsonProperty("at_hash")]
    public string AtHash { get; set; }
    /// 
    ///
    /// 
    [JsonProperty("email")]
    public string Email { get; set; }
    /// 
    ///
    /// 
    [JsonProperty("email_verified")]
    public bool EmailVerified { get; set; }
    /// 
    ///
    /// 
    [JsonProperty("is_private_email")]
    public bool IsPrivateEmail { get; set; }
    /// 
    ///
    /// 
    [JsonProperty("auth_time")]
    public long AuthTime { get; set; }
    /// 
    ///
    /// 
    [JsonProperty("nonce_supported")]
    public bool NonceSupported { get; set; }
}

常见问题

  • POST请求返回400
    1.可能参数不正确;
    2.每次授权信息只能请求一次,成功之后,第二次之后请求均返回400。

  • 本地调试没问题,发布到服务器报错,错误为:
    系统找不到指定的文件。\r\n【具体信息】 在 System.Security.Cryptography.NCryptNative.ImportKey(SafeNCryptProviderHandle provider, Byte[] keyBlob, String format)\r\n 在 System.Security.Cryptography.CngKey.Import(Byte[] keyBlob, String curveName, CngKeyBlobFormat format, CngProvider provider)

解决方案
1.在服务器以管理员身份运行 C:\Windows\System32\cmd.exe ;
2.输入以下命令,注意修改name='hlll'部分,单引号部分为你的IIS上面的网站名称。

 c:\windows\system32\inetsrv\appcmd.exe set config -section:applicationPools "/[name='你的网站名称'].processModel.loadUserProfile:true"

  • 本地调试没问题,发布到服务器报错,错误为:
    用于 ECDsaCng 算法的密钥必须具有 ECDsa 算法组(英文报错为 Keys used with the ECDsaCng algorithm must have an algorithm group of ECDsa)

解决方案
服务器.NET framework升级到4.6.2即可解决问题。

你可能感兴趣的:(C# Sign in with Apple后台接口)