CrazyIter vor 4 Jahren
Ursprung
Commit
45c2783dc7

+ 39 - 0
TEAMModelOS.SDK/Extension/JwtAuth/Filters/BlackListJwtSecurityTokenHandler.cs

@@ -0,0 +1,39 @@
+using Microsoft.IdentityModel.Tokens;
+using System;
+using System.Collections.Generic;
+using System.IdentityModel.Tokens.Jwt;
+using System.Security.Claims;
+using System.Text;
+using TEAMModelOS.SDK.Context.Exception;
+using TEAMModelOS.SDK.Helper.Security.ShaHash;
+
+namespace TEAMModelOS.SDK.Extension.JwtAuth.Filters
+{
+    public class BlackListJwtSecurityTokenHandler : JwtSecurityTokenHandler
+    {
+
+
+        public BlackListJwtSecurityTokenHandler()
+        {
+
+        }
+
+        public override ClaimsPrincipal ValidateToken(string token, TokenValidationParameters validationParameters,
+            out SecurityToken validatedToken)
+        {
+            var claimsPrincipal = base.ValidateToken(token, validationParameters, out validatedToken);
+
+            //解析ClaimsPrincipal取出UserId、Iat和Jti
+            //具体的验证步骤有两个:
+            //- 到Redis查找该用户的Token失效时间,如果当前Token的颁发时间在此之前就是无效的;
+            //- 到Redis的黑名单里判断是否存在该Token; 
+            //通过Redis验证Token
+            string sha = ShaHashHelper.GetSHA1(token);
+            if (RedisHelper.Exists("jwt:" + sha))
+            {
+                throw new BizException("登录失效!", 401);
+            }
+            return claimsPrincipal;
+        }
+    }
+}

+ 22 - 1
TEAMModelOS.SDK/Extension/JwtAuth/JwtAuthExtension.cs

@@ -9,6 +9,7 @@ using System;
 using System.Threading.Tasks;
 using TEAMModelOS.SDK.Context.Configuration;
 using TEAMModelOS.SDK.Helper.Security.RSACrypt;
+using TEAMModelOS.SDK.Extension.JwtAuth.Filters;
 
 namespace TEAMModelOS.SDK.Extension.JwtAuth
 {
@@ -20,7 +21,9 @@ namespace TEAMModelOS.SDK.Extension.JwtAuth
             // var creds = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["SecurityKey"]), SecurityAlgorithms.RsaSha256Signature);
             //var creds = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["SecurityKey"]));
             string path = BaseConfigModel.ContentRootPath;
-            RsaSecurityKey creds = new RsaSecurityKey(RsaHelper.LoadCertificateFile(path + "/private.pem"));
+           // RsaSecurityKey creds = new RsaSecurityKey(RsaHelper.LoadCertificateFile(path + "/private.pem"));
+
+            SecurityKey creds = RsaHelper.GenerateValidationKey(path + "/public.pem");
             //RsaSecurityKey creds = new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["SecurityKey"])), SecurityAlgorithms.RsaSha256Signature);
             // 令牌验证参数
             var tokenValidationParameters = new TokenValidationParameters
@@ -42,9 +45,13 @@ namespace TEAMModelOS.SDK.Extension.JwtAuth
             };
             services.AddAuthentication(x => {
                 x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
+                x.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
                 x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
             }).AddJwtBearer(o =>
             {
+                ///https://blog.csdn.net/sinat_14899485/article/details/88591848 jwt 黑名单
+                //o.SecurityTokenValidators.Clear();
+                o.SecurityTokenValidators.Add(new BlackListJwtSecurityTokenHandler()); /// 自定义黑名单拦截
                 o.TokenValidationParameters = tokenValidationParameters;
                 o.Events = new JwtBearerEvents
                 {
@@ -74,6 +81,20 @@ namespace TEAMModelOS.SDK.Extension.JwtAuth
 
                 };
             });
+            if (services == null) throw new ArgumentNullException(nameof(services));
+            // 1【授权】、这个和上边的异曲同工,好处就是不用在controller中,写多个 roles 。
+            // 然后这么写 [Authorize(Policy = "Admin")]
+            services.AddAuthorization(options =>
+            {
+                //options.AddPolicy(Constant.Role_Root, policy => policy.RequireRole("root").Build());
+                //options.AddPolicy(Constant.Role_Admin, policy => policy.RequireRole("admin").Build());
+                //options.AddPolicy(Constant.Role_Lecturer, policy => policy.RequireRole("lecturer").Build());
+                //options.AddPolicy(Constant.Role_Learner, policy => policy.RequireRole("learner").Build());
+                //options.AddPolicy(Constant.Role_RootAdmin, policy => policy.RequireRole("root", "admin").Build());
+                //options.AddPolicy(Constant.Role_WebAll, policy => policy.RequireRole("root", "admin", "lecturer").Build());
+                //options.AddPolicy(Constant.Role_LecturerLearner, policy => policy.RequireRole("lecturer", "learner").Build());
+
+            });
         }
     }
 }

+ 2 - 4
TEAMModelOS.SDK/Extension/JwtAuth/JwtHelper/JwtHelper.cs

@@ -46,11 +46,9 @@ namespace TEAMModelOS.SDK.Extension.JwtAuth.JwtHelper
             //claims.AddRange(claimModel.Roles.Select(s=>new Claim(JwtClaimTypes.Role, s)));
             //claims.AddRange(claimModel.Claims.Select(s => new Claim(ClaimTypes.Role, s)));
             string path = BaseConfigModel.ContentRootPath;
-            RSACryptoServiceProvider provider = RsaHelper.LoadCertificateFile(path + "/private.pem");
-            RsaSecurityKey rsaSecurity = new RsaSecurityKey(provider);
-            var creds =new SigningCredentials(rsaSecurity, SecurityAlgorithms.RsaSha256);
-
+            var creds = RsaHelper.GenerateSigningCredentials(path + "/private.pem");
             var jwt = new JwtSecurityToken(
+                issuer: setting.Issuer,
                 claims:claims,
                 signingCredentials:creds
                 );

+ 0 - 62
TEAMModelOS.SDK/Helper/Security/RSACrypt/RSACrypt.cs

@@ -1,62 +0,0 @@
-using System.Collections.Generic;
-using System.Security.Cryptography;
-using System.Text;
-using XC.Framework.Security.RSAUtil;
-
-namespace TEAMModelOS.SDK.Helper.Security.RSACrypt
-{
-    /// <summary>
-    /// RSA加密解密
-    /// https://github.com/stulzq/RSAUtil
-    /// </summary>
-    public class RSACrypt
-    {
-
-        private readonly RsaPkcs1Util _RsaUtil;
-        private readonly Encoding _encoding;
-
-        /// <summary>
-        /// 获得私钥和公钥
-        /// [0]=privateKey  私钥 
-        /// [1]=publicKey  公钥
-        /// </summary>
-        /// <returns></returns>
-        public static List<string> GetKey()
-        {
-            return RsaKeyGenerator.Pkcs1Key(2048, true);
-        }
-
-        /// <summary>
-        /// 实例化
-        /// </summary>
-        /// <param name="encoding">编码类型</param>
-        /// <param name="privateKey">私钥</param>
-        /// <param name="publicKey">公钥</param>
-        public RSACrypt(string privateKey, string publicKey)
-        {
-            _encoding = Encoding.UTF8;
-            _RsaUtil = new RsaPkcs1Util(_encoding, publicKey, privateKey, 1024);
-        }
-
-        /// <summary>
-        /// 加密
-        /// </summary>
-        /// <param name="code">加密代码</param>
-        /// <returns></returns>
-        public string Encrypt(string code)
-        {
-            return _RsaUtil.Encrypt(code, RSAEncryptionPadding.Pkcs1);
-        }
-
-        /// <summary>
-        /// 解密
-        /// </summary>
-        /// <param name="code">解密代码</param>
-        /// <returns></returns>
-        public string Decrypt(string code)
-        {
-            return _RsaUtil.Decrypt(code, RSAEncryptionPadding.Pkcs1);
-        }
-
-    }
-}

+ 211 - 0
TEAMModelOS.SDK/Helper/Security/RSACrypt/RSAUtils.cs

@@ -0,0 +1,211 @@
+using System;
+using System.IO;
+using System.Security.Cryptography;
+namespace TEAMModelOS.SDK.Helper.Security.RSACrypt
+{
+    public static class RSAUtils
+    {
+        private const string PrivateKeyHeader = "-----BEGIN RSA PRIVATE KEY-----";
+        private const string PrivateKeyFooter = "-----END RSA PRIVATE KEY-----";
+
+        public static RSA? FromPrivateKey(string key)
+        {
+            key = key.Trim();
+
+            if (!key.StartsWith(PrivateKeyHeader) || !key.EndsWith(PrivateKeyFooter))
+                throw new ArgumentException("Expect PKCS#1 PEM format key");
+
+            key = key.Substring(PrivateKeyHeader.Length, key.Length - PrivateKeyHeader.Length - PrivateKeyFooter.Length);
+
+            // Convert.FromBase64String ignores whitespace
+            byte[] keyBytes = Convert.FromBase64String(key);
+            return DecodeRSAPrivateKey(keyBytes);
+        }
+
+        //------- Parses binary ans.1 RSA private key; returns RSA ---
+        static RSA? DecodeRSAPrivateKey(byte[] privateKey)
+        {
+
+            // ---------  Set up stream to decode the asn.1 encoded RSA private key  ------
+            using var mem = new MemoryStream(privateKey);
+            using var binr = new BinaryReader(mem);
+            byte bt = 0;
+            ushort twobytes = 0;
+            twobytes = binr.ReadUInt16();
+            if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
+                binr.ReadByte();    //advance 1 byte
+            else if (twobytes == 0x8230)
+                binr.ReadInt16();   //advance 2 bytes
+            else
+                return null;
+
+            twobytes = binr.ReadUInt16();
+            if (twobytes != 0x0102) //version number
+                return null;
+            bt = binr.ReadByte();
+            if (bt != 0x00)
+                return null;
+
+            // ------- create RSA from private key RSAParameters -----
+            var rsaParameters = new RSAParameters
+            {
+                Modulus = binr.ReadBytes(GetIntegerSize(binr)),
+                Exponent = binr.ReadBytes(GetIntegerSize(binr)),
+                D = binr.ReadBytes(GetIntegerSize(binr)),
+                P = binr.ReadBytes(GetIntegerSize(binr)),
+                Q = binr.ReadBytes(GetIntegerSize(binr)),
+                DP = binr.ReadBytes(GetIntegerSize(binr)),
+                DQ = binr.ReadBytes(GetIntegerSize(binr)),
+                InverseQ = binr.ReadBytes(GetIntegerSize(binr)),
+            };
+            return RSA.Create(rsaParameters);
+        }
+
+        private const string PublicKeyHeader = "-----BEGIN PUBLIC KEY-----";
+        private const string PublicKeyFooter = "-----END PUBLIC KEY-----";
+
+        public static RSA? FromPublicKey(string key)
+        {
+            key = key.Trim();
+
+            if (!key.StartsWith(PublicKeyHeader) || !key.EndsWith(PublicKeyFooter))
+                throw new ArgumentException("Expect PKCS#1 PEM format key");
+
+            key = key.Substring(PublicKeyHeader.Length, key.Length - PublicKeyHeader.Length - PublicKeyFooter.Length);
+
+            // Convert.FromBase64String ignores whitespace
+            byte[] keyBytes = Convert.FromBase64String(key);
+            return DecodeX509PublicKey(keyBytes);
+        }
+
+        static RSA? DecodeX509PublicKey(byte[] x509Key)
+        {
+            // encoded OID sequence for  PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1"
+            byte[] seqOid = { 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00 };
+            // ---------  Set up stream to read the asn.1 encoded SubjectPublicKeyInfo blob  ------
+            using var mem = new MemoryStream(x509Key);
+            using var binr = new BinaryReader(mem);
+            ushort twobytes = binr.ReadUInt16();
+            switch (twobytes)
+            {
+                case 0x8130:
+                    binr.ReadByte();    //advance 1 byte
+                    break;
+                case 0x8230:
+                    binr.ReadInt16();   //advance 2 bytes
+                    break;
+                default:
+                    return null;
+            }
+
+            byte[] seq = binr.ReadBytes(15);
+            if (!CompareBytearrays(seq, seqOid))  //make sure Sequence for OID is correct
+                return null;
+
+            twobytes = binr.ReadUInt16();
+            if (twobytes == 0x8103) //data read as little endian order (actual data order for Bit String is 03 81)
+                binr.ReadByte();    //advance 1 byte
+            else if (twobytes == 0x8203)
+                binr.ReadInt16();   //advance 2 bytes
+            else
+                return null;
+
+            byte bt = binr.ReadByte();
+            if (bt != 0x00)     //expect null byte next
+                return null;
+
+            twobytes = binr.ReadUInt16();
+            if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
+                binr.ReadByte();    //advance 1 byte
+            else if (twobytes == 0x8230)
+                binr.ReadInt16();   //advance 2 bytes
+            else
+                return null;
+
+            twobytes = binr.ReadUInt16();
+            byte lowbyte = 0x00;
+            byte highbyte = 0x00;
+
+            if (twobytes == 0x8102) //data read as little endian order (actual data order for Integer is 02 81)
+                lowbyte = binr.ReadByte();  // read next bytes which is bytes in modulus
+            else if (twobytes == 0x8202)
+            {
+                highbyte = binr.ReadByte(); //advance 2 bytes
+                lowbyte = binr.ReadByte();
+            }
+            else
+                return null;
+            byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };   //reverse byte order since asn.1 key uses big endian order
+            int modsize = BitConverter.ToInt32(modint, 0);
+
+            byte firstbyte = binr.ReadByte();
+            binr.BaseStream.Seek(-1, SeekOrigin.Current);
+
+            if (firstbyte == 0x00)
+            {   //if first byte (highest order) of modulus is zero, don't include it
+                binr.ReadByte();    //skip this null byte
+                modsize -= 1;   //reduce modulus buffer size by 1
+            }
+
+            byte[] modulus = binr.ReadBytes(modsize); //read the modulus bytes
+
+            if (binr.ReadByte() != 0x02)            //expect an Integer for the exponent data
+                return null;
+            int expbytes = binr.ReadByte();        // should only need one byte for actual exponent data (for all useful values)
+            byte[] exponent = binr.ReadBytes(expbytes);
+
+            // ------- create RSA from public key RSAParameters -----
+            var rsaParameters = new RSAParameters
+            {
+                Modulus = modulus,
+                Exponent = exponent
+            };
+            return RSA.Create(rsaParameters);
+        }
+
+        private static int GetIntegerSize(BinaryReader binr)
+        {
+            byte bt = binr.ReadByte();
+            if (bt != 0x02)     //expect integer
+                return 0;
+            bt = binr.ReadByte();
+
+            int count;
+            if (bt == 0x81)
+                count = binr.ReadByte();    // data size in next byte
+            else
+                if (bt == 0x82)
+            {
+                byte highbyte = binr.ReadByte();
+                byte lowbyte = binr.ReadByte();
+                byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };
+                count = BitConverter.ToInt32(modint, 0);
+            }
+            else
+            {
+                count = bt;     // we already have the data size
+            }
+
+            while (binr.ReadByte() == 0x00)
+            {   //remove high order zeros in data
+                count -= 1;
+            }
+            binr.BaseStream.Seek(-1, SeekOrigin.Current);     //last ReadByte wasn't a removed zero, so back up a byte
+            return count;
+        }
+
+        static bool CompareBytearrays(byte[] a, byte[] b)
+        {
+            if (a.Length != b.Length)
+                return false;
+            int i = 0;
+            foreach (byte c in a)
+            {
+                if (c != b[i])
+                    return false;
+                i++;
+            }
+            return true;
+        }
+    }
+}

+ 12 - 158
TEAMModelOS.SDK/Helper/Security/RSACrypt/RsaHelper.cs

@@ -1,4 +1,5 @@
-using System;
+using Microsoft.IdentityModel.Tokens;
+using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Security.Cryptography;
@@ -7,167 +8,20 @@ using TEAMModelOS.SDK.Helper.Common.JsonHelper;
 
 namespace TEAMModelOS.SDK.Helper.Security.RSACrypt
 {
-    public static class RsaHelper
+    public class RsaHelper
     {
-        public static string RSASign(string data, string privateKeyPem)
+        public static SigningCredentials GenerateSigningCredentials(string file)
         {
-            RSACryptoServiceProvider rsaCsp = LoadCertificateFile(privateKeyPem);
-            byte[] dataBytes = Encoding.UTF8.GetBytes(data);
-            byte[] signatureBytes = rsaCsp.SignData(dataBytes, "SHA1");
-            return Convert.ToBase64String(signatureBytes);
+            var privateRSA = RSAUtils.FromPrivateKey(File.ReadAllText(file));
+            var signingKey = new RsaSecurityKey(privateRSA);
+            var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.RsaSha256);
+            return signingCredentials;
         }
-
-        private static byte[] GetPem(string type, byte[] data)
-        {
-            string pem = Encoding.UTF8.GetString(data);
-            string header = String.Format("-----BEGIN {0}-----\\n", type);
-            string footer = String.Format("-----END {0}-----", type);
-            int start = pem.IndexOf(header) + header.Length;
-            int end = pem.IndexOf(footer, start);
-            string base64 = pem.Substring(start, (end - start));
-            return Convert.FromBase64String(base64);
-        }
-        public static string LoadCertificateFileToSting(string filename)
-        {
-            FileStream fs = System.IO.File.OpenRead(filename);
-            byte[] data = new byte[fs.Length];
-            byte[] res = null;
-            fs.Read(data, 0, data.Length);
-            if (data[0] != 0x30)
-            {
-                res = GetPem("RSA PRIVATE KEY", data);
-            }
-            return res.ToJson();
-        }
-        public static RSACryptoServiceProvider LoadCertificateFile(string filename)
-        {
-            FileStream fs = System.IO.File.OpenRead(filename);
-            byte[] data = new byte[fs.Length];
-            byte[] res = null;
-            fs.Read(data, 0, data.Length);
-            if (data[0] != 0x30)
-            {
-                res = GetPem("RSA PRIVATE KEY", data);
-            }
-            string ss = res.ToJson();
-            RSACryptoServiceProvider rsa = DecodeRSAPrivateKey(res);
-            return rsa;
-        }
-
-        private static RSACryptoServiceProvider DecodeRSAPrivateKey(byte[] privkey)
-        {
-            byte[] MODULUS, E, D, P, Q, DP, DQ, IQ;
-
-            // --------- Set up stream to decode the asn.1 encoded RSA private key ------
-            MemoryStream mem = new MemoryStream(privkey);
-            BinaryReader binr = new BinaryReader(mem);  //wrap Memory Stream with BinaryReader for easy reading
-            byte bt = 0;
-            ushort twobytes = 0;
-            int elems = 0;
-            try
-            {
-                twobytes = binr.ReadUInt16();
-                if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
-                    binr.ReadByte();    //advance 1 byte
-                else if (twobytes == 0x8230)
-                    binr.ReadInt16();    //advance 2 bytes
-                else
-                    return null;
-
-                twobytes = binr.ReadUInt16();
-                if (twobytes != 0x0102) //version number
-                    return null;
-                bt = binr.ReadByte();
-                if (bt != 0x00)
-                    return null;
-
-
-                //------ all private key components are Integer sequences ----
-                elems = GetIntegerSize(binr);
-                MODULUS = binr.ReadBytes(elems);
-
-                elems = GetIntegerSize(binr);
-                E = binr.ReadBytes(elems);
-
-                elems = GetIntegerSize(binr);
-                D = binr.ReadBytes(elems);
-
-                elems = GetIntegerSize(binr);
-                P = binr.ReadBytes(elems);
-
-                elems = GetIntegerSize(binr);
-                Q = binr.ReadBytes(elems);
-
-                elems = GetIntegerSize(binr);
-                DP = binr.ReadBytes(elems);
-
-                elems = GetIntegerSize(binr);
-                DQ = binr.ReadBytes(elems);
-
-                elems = GetIntegerSize(binr);
-                IQ = binr.ReadBytes(elems);
-                // ------- create RSACryptoServiceProvider instance and initialize with public key -----
-                CspParameters CspParameters = new CspParameters
-                {
-                    Flags = CspProviderFlags.UseMachineKeyStore
-                };
-                RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(1024, CspParameters);
-                RSAParameters RSAparams = new RSAParameters
-                {
-                    Modulus = MODULUS,
-                    Exponent = E,
-                    D = D,
-                    P = P,
-                    Q = Q,
-                    DP = DP,
-                    DQ = DQ,
-                    InverseQ = IQ
-                };
-                RSA.ImportParameters(RSAparams);
-                return RSA;
-            }
-            catch (Exception ex)
-            {
-                throw new Exception("", ex);
-            }
-            finally
-            {
-                binr.Close();
-            }
-        }
-
-        private static int GetIntegerSize(BinaryReader binr)
+        public static SecurityKey GenerateValidationKey(string file)
         {
-            byte bt = 0;
-            byte lowbyte = 0x00;
-            byte highbyte = 0x00;
-            int count = 0;
-            bt = binr.ReadByte();
-            if (bt != 0x02)        //expect integer
-                return 0;
-            bt = binr.ReadByte();
-
-            if (bt == 0x81)
-                count = binr.ReadByte();    // data size in next byte
-            else
-                if (bt == 0x82)
-            {
-                highbyte = binr.ReadByte();    // data size in next 2 bytes
-                lowbyte = binr.ReadByte();
-                byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };
-                count = BitConverter.ToInt32(modint, 0);
-            }
-            else
-            {
-                count = bt;        // we already have the data size
-            }
-
-            while (binr.ReadByte() == 0x00)
-            {    //remove high order zeros in data
-                count -= 1;
-            }
-            binr.BaseStream.Seek(-1, SeekOrigin.Current);        //last ReadByte wasn't a removed zero, so back up a byte
-            return count;
+            var publicRSA = RSAUtils.FromPublicKey(File.ReadAllText(file));
+            var signingKey = new RsaSecurityKey(publicRSA);
+            return signingKey;
         }
     }
 }

+ 19 - 0
TEAMModelOS/Controllers/Core/BlobController.cs

@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace TEAMModelOS.Controllers.Core
+{
+    public class BlobController
+    {
+        /// <summary>
+        /// 资源,题目关联,htex关联,学习活动关联,教师基本信息关联
+        /// </summary>
+        public string[] teacher = new string[] { "res","item","htex","task","info"};
+        /// <summary>
+        /// 资源,题目关联,htex关联,学习活动学生上传文件关联,基本信息关联,教室平面图关联,评测冷数据关联
+        /// </summary>
+        public string[] school = new string[] { "res", "item", "htex", "task", "info","room","exam"};
+    }
+}

+ 1 - 1
TEAMModelOS/Startup.cs

@@ -119,7 +119,7 @@ namespace TEAMModelOS
             //HttpContextAccessor,并用来访问HttpContext。
             services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
             //引入Jwt配置
-            // services.JwtAuth(Configuration.GetSection("JwtSetting"));
+           services.JwtAuth(Configuration.GetSection("JwtSetting"));
             //注入CSRedis
             var csredis = new CSRedis.CSRedisClient(Configuration.GetSection("Azure:Redis:ConnectionString").Get<string>());
            // CSRedis.CSRedisClient.Serialize = obj =>System.Text.Json.JsonSerializer.Serialize(obj);