• ASP.NET Sign In With Apple 后端验证(C#)


    苹果在2019年 9 月12 号更新了审核指南,加入 4.8 Sign in with Apple 一条,要求所有使用第三方登录 的 App,都必须接入 Sign in with Apple。已经上架的 App 需在 2020 年 4 月 前完成接入工作,新上架 App(如果支持三方登录)必须接入Sign in with Apple,否则将被拒。

    App登录成功后,需要将获取到的 identityToken、code等信息发送给后台,然后由后台调用 Apple 的后台API,来验证用户的真实性,从而完成验证。

    本文讲述C#基于授权码的Sign In With Apple后端验证:

    client_secret的构建方法

    先在后台生成授权应用APP ID的密钥KEY文件,然后下载密钥文件,此文件只能下载一次,请妥善保存,格式样例:

    #密钥KEY格式样例
    -----BEGIN PRIVATE KEY-----
      BASE64编码后的密钥
    -----END PRIVATE KEY-----
    

    秘钥读取

      /// <summary>
            /// 获取P8
    
            /// </summary>
    
            /// <returns></returns>
    
            private CngKey GetPrivateKey()
    
            {
    
                const string privateKey = 
    @"BASE64编码后的密钥"; // contents of .p8 file
    
    
    
                var cngKey = CngKey.Import(
    
    
    
                  Convert.FromBase64String(privateKey),
    
                  CngKeyBlobFormat.Pkcs8PrivateBlob);
    
                return cngKey;
    
            }
    
    
    
            private static SigningCredentials CreateSigningCredentials(string keyId, 
    ECDsa algorithm)
    
            {
    
                var key = new ECDsaSecurityKey(algorithm) { KeyId = keyId };
    
                return new SigningCredentials(key, 
    SecurityAlgorithms.EcdsaSha256Signature);
    
            }
    
    
    验证
      /// <summary>
    
            /// 检验生成的授权码是正确的,需要给出正确的授权码
    
            /// </summary>
    
            /// <param name="authorizationCode">授权码</param>
    
            /// <param name="appUserId">apple用户ID</param>
    
            /// <returns></returns>
    
            public async Task<string> TestAppleSign(string authorizationCode, string 
    appUserId)
    
            {
    
                var httpClientHandler = new HttpClientHandler
    
                {
    
                    ServerCertificateCustomValidationCallback = (message, certificate2, 
    arg3, arg4) => true
    
                };
    
                var httpClient = new HttpClient(httpClientHandler, true)
    
                {
    
                    //超时时间设置长一点点,有时候响应超过3秒,根据情况设置
    
                    //我这里是单元测试,防止异常所以写30秒
    
                    Timeout = TimeSpan.FromSeconds(30)
    
                };
    
                var newToken = CreateSecret();
    
                var clientId = "";//需要IOS提供
    
                var datas = new Dictionary<string, string>()
    
                {
    
                    {"client_id", clientId},
    
                    {"grant_type", "authorization_code"},//固定authorization_code
    
                    {"code",authorizationCode },//authorizationCode },//授权码,前端验证登录给予
    
                    {"client_secret",newToken} //client_secret,后面方法生成
    
                };
    
                //x-www-form-urlencoded 使用FormUrlEncodedContent
    
                var formdata = new FormUrlEncodedContent(datas);
    
                var result = await 
    httpClient.PostAsync("https://appleid.apple.com/auth/token", formdata);
    
                var re = await result.Content.ReadAsStringAsync();
    
                if (result.IsSuccessStatusCode)
    
                {
    
                    var deserializeObject = 
    JsonConvert.DeserializeObject<TokenResult>(re);
    
                    var jwtPlayload = DecodeJwtPlayload(deserializeObject.IdToken);
    
                    if (!jwtPlayload.Aud.Equals(appUserId))//appUserId,前端验证登录给予
    
                    {
    
                        return await Task<string>.FromResult(jwtPlayload.Aud);
    
                    }
    
                    else
    
                    {
    
                        //请根据re的返回值,查看上面的错误表格
    
                        return await Task<string>.FromResult(re);
    
    
    
                    }
    
                }
    
                else
    
                {
    
                    //请根据re的返回值,查看上面的错误表格
    
                    return await Task<string>.FromResult(re);
    
    
    
                }
    
            }
     /// <summary>
    /// 生成CreateSecret
    /// </summary>
    private string CreateSecret()
    
            {
    
                var handler = new JwtSecurityTokenHandler();
    
                var subject = new Claim("sub", "");//需要IOS提供 
    
                var tokenDescriptor = new SecurityTokenDescriptor()
    
                {
    
                    Audience = "https://appleid.apple.com",//固定值
    
                    Issuer = "",//team ID,需要IOS提供                                 
    
                    IssuedAt = DateTime.UtcNow.AddDays(-1),
    
                    NotBefore = DateTime.UtcNow.AddDays(-1),
    
                    Subject = new ClaimsIdentity(new[] { subject }),
    
                };
    
    
    
                var algorithm = new ECDsaCng(GetPrivateKey());
    
                {
    
                    tokenDescriptor.SigningCredentials = 
    CreateSigningCredentials("", algorithm);//p8私钥文件得Key,需要IOS提供
    
                    var clientSecret = handler.CreateEncodedJwt(tokenDescriptor);
    
                    return clientSecret;
    
                }
    
    
    
            }
    
    
    
    返回值样例
    {"access_token":"a0996b16cfb674c0eb0d29194c880455b.0.nsww.5fi5MVC-i3AVNhddrNg7Qw",
    "token_type":"Bearer",
    "expires_in":3600,
    "refresh_token":"r9ee922f1c8b048208037f78cd7dfc91a.0.nsww.KlV2TeFlTr7YDdZ0KtvEQQ","id_token":"eyJraWQiOiJBSURPUEsxIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLnNreW1pbmcuYXBwbGVsb2dpbmRlbW8iLCJleHAiOjE1NjU2NjU1OTQsImlhdCI6MTU2NTY2NDk5NCwic3ViIjoiMDAwMjY2LmRiZTg2NWIwYWE3MjRlMWM4ODM5MDIwOWI5YzdkNjk1LjAyNTYiLCJhdF9oYXNoIjoiR0ZmODhlX1ptc0pqQ2VkZzJXem85ZyIsImF1dGhfdGltZSI6MTU2NTY2NDk2M30.J6XFWmbr0a1hkJszAKM2wevJF57yZt-MoyZNI9QF76dHfJvAmFO9_RP9-tz4pN4ua3BuSJpUbwzT2xFD_rBjsNWkU-ZhuSAONdAnCtK2Vbc2AYEH9n7lB2PnOE1mX5HwY-dI9dqS9AdU4S_CjzTGnvFqC9H5pt6LVoCF4N9dFfQnh2w7jQrjTic_JvbgJT5m7vLzRx-eRnlxQIifEsHDbudzi3yg7XC9OL9QBiTyHdCQvRdsyRLrewJT6QZmi6kEWrV9E21WPC6qJMsaIfGik44UgPOnNnjdxKPzxUAa-Lo1HAzvHcAX5i047T01ltqvHbtsJEZxAB6okmwco78JQA"}
    
    解析jwt第二部分
     /// <summary>
    
            /// 解析jwt第二部分
    
            /// </summary>
    
            /// <param name="jwtString"></param>
    
            /// <returns></returns>
    
            private JwtPlayload DecodeJwtPlayload(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<JwtPlayload>(decode);
    
                }
    
                catch (Exception e)
    
                {
    
                    throw new Exception(e.Message);
    
                }
    
            }
    
    
    
    所用实体类
       /// <summary>
    
            /// 接口返回值
    
            /// </summary>
    
            public class TokenResult
    
            {
    
                /// <summary>
    
                /// 一个token
    
                /// </summary>
    
                [JsonProperty("access_token")]
    
                public string AccessToken { get; set; }
    
                /// <summary>
    
                /// Bearer
    
                /// </summary>
    
                [JsonProperty("token_type")]
    
                public string TokenType { get; set; }
    
                /// <summary>
    
                ///
    
                /// </summary>
    
                [JsonProperty("expires_in")]
    
                public long ExpiresIn { get; set; }
    
                /// <summary>
    
                /// 一个token
    
                /// </summary>
    
                [JsonProperty("refresh_token")]
    
                public string RefreshToken { get; set; }
    
                /// <summary>
    
                /// "结果是JWT,字符串形式,identityToken 解析后和客户端端做比对
    
                /// </summary>
    
                [JsonProperty("id_token")]
    
                public string IdToken { get; set; }
    
            }
    
            /// <summary>
    
            /// jwt第二部分
    
            /// </summary>
    
            private class JwtPlayload
    
            {
    
                /// <summary>
    
                /// "https://appleid.apple.com"
    
                /// </summary>
    
                [JsonProperty("iss")]
    
                public string Iss { get; set; }
    
                /// <summary>
    
                /// 这个是你的app的bundle identifier
    
                /// </summary>
    
                [JsonProperty("aud")]
    
                public string Aud { get; set; }
    
                /// <summary>
    
                ///
    
                /// </summary>
    
                [JsonProperty("exp")]
    
                public long Exp { get; set; }
    
                /// <summary>
    
                ///
    
                /// </summary>
    
                [JsonProperty("iat")]
    
                public long Iat { get; set; }
    
                /// <summary>
    
                /// 用户ID
    
                /// </summary>
    
                [JsonProperty("sub")]
    
                public string Sub { get; set; }
    
                /// <summary>
    
                ///
    
                /// </summary>
    
                [JsonProperty("at_hash")]
    
                public string AtHash { get; set; }
    
                /// <summary>
    
                ///
    
                /// </summary>
    
                [JsonProperty("email")]
    
                public string Email { get; set; }
    
                /// <summary>
    
                ///
    
                /// </summary>
    
                [JsonProperty("email_verified")]
    
                public bool EmailVerified { get; set; }
    
                /// <summary>
    
                ///
    
                /// </summary>
    
                [JsonProperty("is_private_email")]
    
                public bool IsPrivateEmail { get; set; }
    
                /// <summary>
    
                ///
    
                /// </summary>
    
                [JsonProperty("auth_time")]
    
                public long AuthTime { get; set; }
    
                /// <summary>
    
                ///
    
                /// </summary>
    
                [JsonProperty("nonce_supported")]
    
                public bool NonceSupported { get; set; }
    
            }
    
    
    
  • 相关阅读:
    vmware磁盘空间扩展
    Winrar发现损坏的压缩文件头
    java ASM动态生成类
    使用ffmpeg将任意格式视频转MP4格式
    mongodb导入csv结构化数据
    Vmware黑屏解决方法
    mysql命令行导入结构化数据
    mysql导入慢解决方法
    CategoryPanelGroup动态生成节点
    delphi XE7 判断手机返回键
  • 原文地址:https://www.cnblogs.com/dikeko/p/13618252.html
Copyright © 2020-2023  润新知