• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • YouClaw
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

无缺丶

  • 博客园
  • 联系
  • 订阅
  • 管理

公告

View Post

.net Sign in with apple

苹果应用市场发出规定,所有上架的app,只要有第三方登录的功能,必须加上使用苹果手机登录这一功能。由于我是个.neter,主要做的是后端,所以登录验证这块记录一下,以供参考!

1.Token结构

 var jwt = "eyJraWQiOiJBSURPUEsxIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLnNreW1pbmcuYXBwbGVsb2dpbmRlbW8iLCJleHAiOjE1NjU2NjU1OTQsImlhdCI6MTU2NTY2NDk5NCwic3ViIjoiMDAwMjY2LmRiZTg2NWIwYWE3MjRlMWM4ODM5MDIwOWI5YzdkNjk1LjAyNTYiLCJhdF9oYXNoIjoiR0ZmODhlX1ptc0pqQ2VkZzJXem85ZyIsImF1dGhfdGltZSI6MTU2NTY2NDk2M30.J6XFWmbr0a1hkJszAKM2wevJF57yZt-MoyZNI9QF76dHfJvAmFO9_RP9-tz4pN4ua3BuSJpUbwzT2xFD_rBjsNWkU-ZhuSAONdAnCtK2Vbc2AYEH9n7lB2PnOE1mX5HwY-dI9dqS9AdU4S_CjzTGnvFqC9H5pt6LVoCF4N9dFfQnh2w7jQrjTic_JvbgJT5m7vLzRx-eRnlxQIifEsHDbudzi3yg7XC9OL9QBiTyHdCQvRdsyRLrewJT6QZmi6kEWrV9E21WPC6qJMsaIfGik44UgPOnNnjdxKPzxUAa-Lo1HAzvHcAX5i047T01ltqvHbtsJEZxAB6okmwco78JQA";

 

这块是前端从苹果服务器获取到的token,这个token是jwt的格式的,我们需要对jwt有一些了解

JWT构成

 jwt最多由3部分组成,"{header}.{payload}.{signature}"由“.”区分开来。

第一部分我们称它为头部(header),第二部分称为载荷(payload),第三部分是签证(signature)

header

jwt的头部承载两部分信息:

1、声明类型,这里是jwt

2、声明加密的算法通常是SHA256

完整的头部json如下

{
  'typ': 'JWT',
  'alg': 'HS256'
}

然后将头部进行base64加密,构成第一部分

ewogICd0eXAnOiAnSldUJywKICAnYWxnJzogJ0hTMjU2Jwp9

playload

载荷就是存放有效信息的地方。其中包括(包括但不限于

)如下几个部分:

1、iss:jwt的签发者

2、sub:jwt所面向的用户

3、aud:接收jwt的一方

4、exp:jwt的过期时间,这个日期必须大于签发时间

5、nbf:定义在什么时间之前,该jwt都是不可用的

6、iat:jwt的签发时间

7、jti:jwt的唯一身份标识,主要用来作为一次性token

json信息如下:

{
  "sub":"123456",
  "aud":"Tom"
}

然后进行base64加密,构成第二部分

ewogICJzdWIiOiIxMjM0NTYiLAogICJhdWQiOiJUb20iCn0=

signature

jwt的第三个部分是一个签证信息,这个信息有三部分组成:

1、header(base64后的)

2、payload(base64后的)

3、secret

这个部分需要base64加密后的header和payload使用"."连接组成的字符串,然后通过头部信息的加密方式进行加盐secret组合加密构成了jwt的第三部分。

如何验证苹果传过来的token的有效性?

苹果官方文档给了我们两种验证token的方式

 

 

 

 

 

 

 

这里我们选择第一种方式验证。先去获取公钥,通过访问网址https://appleid.apple.com/auth/keys我们得到如下信息

 

 

 首先我们需要通过头部信息来找到对应的公钥,同时我们知道signature信息是由头部和payload加上钥匙组成的。我们这里可以使用RSACryptoServiceProvider类库导入公钥,然后来验证数据的SHA256的摘要即可

 using (var rsa = new RSACryptoServiceProvider())
            {
                // 导入RSA公钥
                rsa.ImportParameters(new RSAParameters() { Exponent = e, Modulus = n });
                // 验证数据的SHA256摘要
                var signatureVerified = rsa.VerifyData(signedOver, signagure, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
                if (!signatureVerified)
                {


                    issss = false;
                }

            }

完整代码如下:

namespace jwt
{
    class Program
    {
        static void Main(string[] args)
        {

            // jwt最多由3部分组成,"{header}.{payload}.{signature}"
            var jwt = "eyJraWQiOiJBSURPUEsxIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLnNreW1pbmcuYXBwbGVsb2dpbmRlbW8iLCJleHAiOjE1NjU2NjU1OTQsImlhdCI6MTU2NTY2NDk5NCwic3ViIjoiMDAwMjY2LmRiZTg2NWIwYWE3MjRlMWM4ODM5MDIwOWI5YzdkNjk1LjAyNTYiLCJhdF9oYXNoIjoiR0ZmODhlX1ptc0pqQ2VkZzJXem85ZyIsImF1dGhfdGltZSI6MTU2NTY2NDk2M30.J6XFWmbr0a1hkJszAKM2wevJF57yZt-MoyZNI9QF76dHfJvAmFO9_RP9-tz4pN4ua3BuSJpUbwzT2xFD_rBjsNWkU-ZhuSAONdAnCtK2Vbc2AYEH9n7lB2PnOE1mX5HwY-dI9dqS9AdU4S_CjzTGnvFqC9H5pt6LVoCF4N9dFfQnh2w7jQrjTic_JvbgJT5m7vLzRx-eRnlxQIifEsHDbudzi3yg7XC9OL9QBiTyHdCQvRdsyRLrewJT6QZmi6kEWrV9E21WPC6qJMsaIfGik44UgPOnNnjdxKPzxUAa-Lo1HAzvHcAX5i047T01ltqvHbtsJEZxAB6okmwco78JQA";
            var parts = jwt.Split('.');



            // heaer 类似 {"kid": "eXaunmL",  "alg": "RS256"}, kid是key的标志,用来从一堆keys里拿到响应的钥匙
            var header = JsonConvert.DeserializeObject<JObject>(Encoding.UTF8.GetString(FromBase64(parts[0])));

            // payload 类似 {... "sub": "000401.a5bb0bec6491440bb61fa314a50b2a13.0737", "email": "hj7a4dp4ua@privaterelay.appleid.com", ...}
            var payload = JsonConvert.DeserializeObject<JObject>(Encoding.UTF8.GetString(FromBase64(parts[1])));


            // 第三部分是验证码,用来验证数据"{header}.{payload}"
            var signagure = FromBase64(parts[2]);
            var signedOver = Encoding.UTF8.GetBytes(parts[0] + "." + parts[1]);


            // 下载钥匙,并用kid找到响应的签名公钥

            var keysJson = Http_Get("https://appleid.apple.com/auth/keys");
            var keys = JsonConvert.DeserializeObject<JObject>(keysJson)["keys"] as JArray;
            var key = keys.OfType<JObject>().FirstOrDefault(x => (string)x["kid"] == (string)header["kid"]);

            // 这里只支持RS256签名。RS256就是使用RSA算法,用一个RSA公钥,来验证数据的SHA256摘要。
            var alg = (string)key["alg"]; if (alg != "RS256") throw new NotImplementedException();
            var n = FromBase64((string)key["n"]);
            var e = FromBase64((string)key["e"]);
            bool issss = true;
            using (var rsa = new RSACryptoServiceProvider())
            {
                // 导入RSA公钥
                rsa.ImportParameters(new RSAParameters() { Exponent = e, Modulus = n });
                // 验证数据的SHA256摘要
                var signatureVerified = rsa.VerifyData(signedOver, signagure, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
                if (!signatureVerified)
                {


                    issss = false;
                }

            }
            DateTime exp = ConvertStringToDateTime(Convert.ToInt32(payload["exp"]));
            var iat = ConvertStringToDateTime(Convert.ToInt32(payload["iat"]));
            //验证token的的有效期
            if (DateTime.Now > exp || DateTime.Now < iat)
            {


                issss = false;
            }

        }

        static byte[] FromBase64(string base64WithoutPadding)
        {
            var base64 = base64WithoutPadding.Length % 4 == 0
                ? base64WithoutPadding
                : base64WithoutPadding + new string('=', 4 - base64WithoutPadding.Length % 4);

            return Convert.FromBase64String(base64.Replace("-", "+").Replace('_', '/'));
        }
        static DateTime ConvertStringToDateTime(int timeStamp)
        {
            DateTime dtStart = TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1));
            long lTime = ((long)timeStamp * 10000000);
            TimeSpan toNow = new TimeSpan(lTime);
            DateTime targetDt = dtStart.Add(toNow);
            return targetDt;
        }

        public static string Http_Get(string url, int timeout = 25000, string contentType = "application/json; charset=utf-8", bool keepAlive = false)
        {
            HttpWebRequest request = CreateWebRequest(url, "GET", timeout, contentType, keepAlive, false);
            request.ContentLength = 0;
            try
            {
                using (var response = request.GetResponse())
                {
                    Stream stream = response.GetResponseStream();
                    StreamReader reader = new StreamReader(stream, Encoding.UTF8);
                    string retString = reader.ReadToEnd();
                    reader.Close();
                    stream.Close();
                    return retString;
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                if (request != null)
                {
                    request.Abort();
                }
            }
        }

        private static HttpWebRequest CreateWebRequest(string url, string method, int timeout, string contentType, bool keepAlive, bool isnocache = true)
        {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
            request.Method = method;
            request.ContentType = contentType;
            request.Accept = "application/xml,application/xhtml+xml, application/json,text/html, text/javascript;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5";
            if (isnocache)
            {
                request.Headers["Pragma"] = "no-cache";
            }
            request.Timeout = timeout;
            request.AllowAutoRedirect = true;
            request.KeepAlive = keepAlive;
            request.ServicePoint.Expect100Continue = false;
            request.Proxy = null;
            return request;
        }


    }
}

撰写博客主在学习记录,如有不足欢迎指出!

 

 

posted on 2020-08-21 15:27  无缺丶  阅读(206)  评论(1)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3