当前位置: 首页 > 编程日记 > 正文

iOS使用自签名证书实现HTTPS请求

原文链接:http://www.jianshu.com/p/e6a26ecd84aa


由于苹果规定2017年1月1日以后,所有APP都要使用HTTPS进行网络请求,否则无法上架,因此研究了一下在iOS中使用HTTPS请求的实现。相信大家对HTTPS都或多或少有些了解,这里我就不再介绍了,主要功能就是将传输的报文进行加密,提高安全性。

1、证书准备

证书分为两种,一种是花钱向认证的机构购买的证书,服务端如果使用的是这类证书的话,那一般客户端不需要做什么,用HTTPS进行请求就行了,苹果内置了那些受信任的根证书的。另一种是自己制作的证书,使用这类证书的话是不受信任的(当然也不用花钱买),因此需要我们在代码中将该证书设置为信任证书。

我这边使用的是xca来制作了根证书,制作流程请参考http://www.2cto.com/Article/201411/347512.html,由于xca无法导出.jsk的后缀,因此我们只要制作完根证书后以.p12的格式导出就行了,之后的证书制作由命令行来完成。自制一个批处理文件,添加如下命令:

set ip=%1%
md %ip%
keytool -importkeystore -srckeystore ca.p12 -srcstoretype PKCS12 -srcstorepass 123456 -destkeystore ca.jks -deststoretype JKS -deststorepass 123456
keytool -genkeypair -alias server-%ip% -keyalg RSA -keystore ca.jks -storepass 123456 -keypass 123456 -validity 3650 -dname "CN=%ip%, OU=ly, O=hik, L=hz, ST=zj, C=cn"
keytool -certreq -alias server-%ip% -storepass 123456 -file %ip%\server-%ip%.certreq -keystore ca.jks
keytool -gencert -alias ca -storepass 123456 -infile %ip%\server-%ip%.certreq -outfile %ip%\server-%ip%.cer -validity 3650 -keystore ca.jks 
keytool -importcert -trustcacerts -storepass 123456 -alias server-%ip% -file %ip%\server-%ip%.cer -keystore ca.jks
keytool -delete -keystore ca.jks -alias ca -storepass 123456

将上面加粗的ca.p12改成你导出的.p12文件的名称,123456改为你创建证书的密码。

然后在文件夹空白处按住ctrl+shift点击右键,选择在此处打开命令窗口,在命令窗口中输入“start.bat ip/域名”来执行批处理文件,其中start.bat是添加了上述命令的批处理文件,ip/域名即你服务器的ip或者域名。执行成功后会生成一个.jks文件和一个以你的ip或域名命名的文件夹,文件夹中有一个.cer的证书,这边的.jks文件将在服务端使用.cer文件将在客户端使用,到这里证书的准备工作就完成了。

2、服务端配置

由于我不做服务端好多年,只会使用Tomcat,所以这边只讲下Tomcat的配置方法,使用其他服务器的同学请自行查找设置方法。

打开tomcat/conf目录下的server.xml文件将HTTPS的配置打开,并进行如下配置:

<Connector URIEncoding="UTF-8" protocol="org.apache.coyote.http11.Http11NioProtocol" port="8443" maxThreads="200" scheme="https" secure="true" SSLEnabled="true" sslProtocol="TLSv1.2" sslEnabledProtocols="TLSv1.2" keystoreFile="${catalina.base}/ca/ca.jks" keystorePass="123456" clientAuth="false" SSLVerifyClient="off" netZone="你的ip或域名"/>

keystoreFile是你.jks文件放置的目录,keystorePass是你制作证书时设置的密码,netZone填写你的ip或域名。注意苹果要求协议要TLSv1.2以上

3、iOS端配置

首先把前面生成的.cer文件添加到项目中,注意在添加的时候选择要添加的targets。

1.使用NSURLSession进行请求

代码如下:

NSString *urlString = @"https://xxxxxxx";
NSURL *url = [NSURL URLWithString:urlString];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:10.0f];
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request];
[task resume];

需要实现NSURLSessionDataDelegate中的URLSession:didReceiveChallenge:completionHandler:方法来进行证书的校验,代码如下:

- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler {
    NSLog(@"证书认证");
    if ([[[challenge protectionSpace] authenticationMethod] isEqualToString: NSURLAuthenticationMethodServerTrust]) {
        do
        {
            SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust];
            NSCAssert(serverTrust != nil, @"serverTrust is nil");
            if(nil == serverTrust)
                break; /* failed */
            /**
             *  导入多张CA证书(Certification Authority,支持SSL证书以及自签名的CA),请替换掉你的证书名称
             */
            NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"ca" ofType:@"cer"];//自签名证书
            NSData* caCert = [NSData dataWithContentsOfFile:cerPath];

NSCAssert(caCert != nil, @"caCert is nil");
            if(nil == caCert)
                break; /* failed */
           
            SecCertificateRef caRef = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)caCert);
            NSCAssert(caRef != nil, @"caRef is nil");
            if(nil == caRef)
                break; /* failed */
           
            //可以添加多张证书
            NSArray *caArray = @[(__bridge id)(caRef)];
           
            NSCAssert(caArray != nil, @"caArray is nil");
            if(nil == caArray)
                break; /* failed */
           
            //将读取的证书设置为服务端帧数的根证书
            OSStatus status = SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)caArray);
            NSCAssert(errSecSuccess == status, @"SecTrustSetAnchorCertificates failed");
            if(!(errSecSuccess == status))
                break; /* failed */
           
            SecTrustResultType result = -1;
            //通过本地导入的证书来验证服务器的证书是否可信
            status = SecTrustEvaluate(serverTrust, &result);
            if(!(errSecSuccess == status))
                break; /* failed */
            NSLog(@"stutas:%d",(int)status);
            NSLog(@"Result: %d", result);
           
            BOOL allowConnect = (result == kSecTrustResultUnspecified) || (result == kSecTrustResultProceed);
            if (allowConnect) {
                NSLog(@"success");
            }else {
                NSLog(@"error");
            }

/* kSecTrustResultUnspecified and kSecTrustResultProceed are success */
            if(! allowConnect)
            {
                break; /* failed */
            }
           
#if 0
            /* Treat kSecTrustResultConfirm and kSecTrustResultRecoverableTrustFailure as success */
            /*   since the user will likely tap-through to see the dancing bunnies */
            if(result == kSecTrustResultDeny || result == kSecTrustResultFatalTrustFailure || result == kSecTrustResultOtherError)
                break; /* failed to trust cert (good in this case) */
#endif
           
            // The only good exit point
            NSLog(@"信任该证书");
           
            NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
            completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
            return [[challenge sender] useCredential: credential
                          forAuthenticationChallenge: challenge];
           
        }
        while(0);
    }
   
    // Bad dog
    NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
    completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge,credential);
    return [[challenge sender] cancelAuthenticationChallenge: challenge];
}

此时即可成功请求到服务端。

注:调用SecTrustSetAnchorCertificates设置可信任证书列表后就只会在设置的列表中进行验证,会屏蔽掉系统原本的信任列表,要使系统的继续起作用只要调用SecTrustSetAnchorCertificates方法,第二个参数设置成NO即可。

2.使用AFNetworking进行请求

AFNetworking首先需要配置AFSecurityPolicy类,AFSecurityPolicy类封装了证书校验的过程。

/**
 AFSecurityPolicy分三种验证模式:
 AFSSLPinningModeNone:只是验证证书是否在信任列表中
 AFSSLPinningModeCertificate:该模式会验证证书是否在信任列表中,然后再对比服务端证书和客户端证书是否一致
 AFSSLPinningModePublicKey:只验证服务端证书与客户端证书的公钥是否一致
*/

AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
    securityPolicy.allowInvalidCertificates = YES;//是否允许使用自签名证书
    securityPolicy.validatesDomainName = NO;//是否需要验证域名,默认YES

AFHTTPSessionManager *_manager = [AFHTTPSessionManager manager];
    _manager.responseSerializer = [AFHTTPResponseSerializer serializer];
    _manager.securityPolicy = securityPolicy;
    //设置超时
    [_manager.requestSerializer willChangeValueForKey:@"timeoutinterval"];
    _manager.requestSerializer.timeoutInterval = 20.f;
    [_manager.requestSerializer didChangeValueForKey:@"timeoutinterval"];
    _manager.requestSerializer.cachePolicy = NSURLRequestReloadIgnoringCacheData;
    _manager.responseSerializer.acceptableContentTypes  = [NSSet setWithObjects:@"application/xml",@"text/xml",@"text/plain",@"application/json",nil];
 
    __weak typeof(self) weakSelf = self;
    [_manager setSessionDidReceiveAuthenticationChallengeBlock:^NSURLSessionAuthChallengeDisposition(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential *__autoreleasing *_credential) {
       
        SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust];
        /**
         *  导入多张CA证书
         */
        NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"ca" ofType:@"cer"];//自签名证书
        NSData* caCert = [NSData dataWithContentsOfFile:cerPath];
        NSArray *cerArray = @[caCert];
        weakSelf.manager.securityPolicy.pinnedCertificates = cerArray;
       
        SecCertificateRef caRef = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)caCert);
        NSCAssert(caRef != nil, @"caRef is nil");
       
        NSArray *caArray = @[(__bridge id)(caRef)];
        NSCAssert(caArray != nil, @"caArray is nil");
       
        OSStatus status = SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)caArray);
        SecTrustSetAnchorCertificatesOnly(serverTrust,NO);
        NSCAssert(errSecSuccess == status, @"SecTrustSetAnchorCertificates failed");
       
        NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        __autoreleasing NSURLCredential *credential = nil;
        if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
            if ([weakSelf.manager.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
                credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
                if (credential) {
                    disposition = NSURLSessionAuthChallengeUseCredential;
                } else {
                    disposition = NSURLSessionAuthChallengePerformDefaultHandling;
                }
            } else {
                disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
            }
        } else {
            disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        }
       
        return disposition;
    }];

上述代码通过给AFHTTPSessionManager重新设置证书验证回调来自己验证证书,然后将自己的证书加入到可信任的证书列表中,即可通过证书的校验。

由于服务端使用.jks是一个证书库,客户端获取到的证书可能不止一本,我这边获取到了两本,具体获取到基本可通过SecTrustGetCertificateCount方法获取证书个数,AFNetworking在evaluateServerTrust:forDomain:方法中,AFSSLPinningMode的类型为AFSSLPinningModeCertificate和AFSSLPinningModePublicKey的时候都有校验服务端的证书个数与客户端信任的证书数量是否一样,如果不一样的话无法请求成功,所以这边我就修改他的源码,当有一个校验成功时即算成功。

当类型为AFSSLPinningModeCertificate时

return trustedCertificateCount == [serverCertificates count] - 1;

为AFSSLPinningModePublicKey时

return trustedPublicKeyCount > 0 && ((self.validatesCertificateChain) || (!self.validatesCertificateChain && trustedPublicKeyCount >= 1));

去掉了第二块中的trustedPublicKeyCount == [serverCertificates count]的条件。

这边使用的AFNetworking的版本为2.5.3,如果其他版本有不同之处请自行根据实际情况修改。

demo地址:https://github.com/fengling2300/networkTest



文/浪投王(简书作者)
原文链接:http://www.jianshu.com/p/e6a26ecd84aa
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

相关文章:

蜜蜂实训平台c语言考试答案,北理c语言上机答案(全)

小蜜蜂的不过,麻烦大家上百度找一下~1 Hello world让我们用C语言向世界问好。 最早的程序基本是这个样子的&#xff1a; 程序一&#xff1a;#include void main() {printf("Hello world.\n"); }程序二&#xff1a;#include int main() {printf("Hello world.\n&…

ecshop目录结构

ECShop 最新程序 的结构图及各文件相应功能介绍ECShop文件结构目录┣ activity.php 活动列表┣ affiche.php 广告处理文件┣ affiliate.php 生成商品列表┣ article.php 文章内容┣ article_cat.php文章分类┣ auction.php 拍卖前台文件┣ brand.php 品牌列表┣ captcha.php 生…

一劳永逸,iOS 网页视图控制器通用类封装

原文链接&#xff1a;http://www.jianshu.com/p/553424763585随着 H5 的发展&#xff0c;在 iOS 开发中&#xff0c;网页视图的使用率逐渐提升&#xff0c;为了增加代码封装度、减轻开发负担&#xff0c;因此通常会对网页视图进行通用类的封装&#xff0c;本文简单讲述网页视图…

enyo官方开发入门教程翻译一Layout之Fittables

Fittables Fittable包帮助你创建布局更合理的使用可用空间&#xff08;app都有这样的布局需求&#xff0c;但使用以前的web技术很难做到这一点&#xff09;。个人观点&#xff0c;现在移动Web开发框架中有很多都支持自适应布局&#xff0c;如senchatouch&#xff0c;但是enyo的…

c语言 栈结构存放数据类型,数据结构——栈的详解

栈和队列是两种重要的线性结构&#xff0c;从数据结构的角度看&#xff0c;栈和队列也是线性表&#xff0c;其特殊性在于栈和队列的基本操作是线性表的子集。他们是操作受限的线性表&#xff0c;因此&#xff0c;可称为限定性的数据结构。但从数据类型角度看&#xff0c;他们是…

Verlet Integration

Verlet Integration Verlet 积分法是一种用于求解牛顿运动方程的数值方法&#xff0c;被广泛运用于动力学模拟以及视频游戏中。尔莱算法的优点在于&#xff1a;数值稳定性比简单的欧拉方法高很多&#xff0c;并保持了物理系统中的时间可逆性与相空间体积元体积守恒的性质。 基本…

[译] iOS 开发之新版 APNs 搭建必备知识

原文链接&#xff1a;http://www.jianshu.com/p/d8dba6c2c07a本文的大部分内容是对苹果关于 APNs 官方文档的翻译以及整理。 —— 由 稀土君 分享本文的大部分内容是对苹果关于APNs官方文档的翻译以及整理。 一、设备token和消息的生命周期 关于设备token以及推送消息的生命周期…

使用Application.GetResourceStream从XAP安装包加载任意资源

不得不说&#xff0c;WP7开发的资料真的是太少了&#xff0c;国内有句话叫“天下文章一大抄”&#xff0c;查Application.GetResourceStream的用法&#xff0c;找遍了整个网络&#xff0c;无非就那一两篇&#xff0c;而且写得还不完整&#xff0c;包括微软官方的例子。在花了近…

c语言知道算法写不出代码,这个代码怎么写算法啊,求教,我真的不会写算法怎么办#incl...

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼这个代码怎么写算法啊&#xff0c;求教&#xff0c;我真的不会写算法怎么办#include "stdio.h"#define N 3 //学生数3。struct Student //建立结构体。{int num;char name[20];float score[3];float aver;};int main(){v…

猎豹MFC--列表控件ListControl

ListControl添加变量&#xff1a;关联控件变量&#xff1a;初始化&#xff1a;设置样式 添加列标题&#xff1a;添加位图资源&#xff1a;添加位图变量&#xff1a;创建图像列表&#xff1a;并把图像列表 给 列表控件使用&#xff1a;双击添加行按钮&#xff1a;添加第2列时 不…

iOS 命令行自动打包 (archive)

原文链接&#xff1a;http://www.jianshu.com/p/2247f76404ebiOS 开发工程师在测试修复 bug 的过程中&#xff0c;一般会存在频繁打包的情况&#xff0c;如果一步步在 xcode 中点击 archive&#xff0c;下一步&#xff0c;下一步。。。这样太浪费我们的时间了。下面我们来介绍在…

看看现在大型网站都是用什么语言写的 ?

看看现在大型网站都是用什么语言写的 &#xff1f; 不排除一个网站用多种技术&#xff01;如淘宝是Java php&#xff0c;底层是java&#xff0c;表现层是php。新浪&#xff0c;网易&#xff0c;腾讯应该也是用了多种技术。 据说是这样的&#xff1a;php&#xff0c;新浪&#…

数据结构 算法与应用C 语言描述第六章,数据结构算法与应用-C语言描述002.pdf

下载下载第2 章 程 序 性 能以下是本章中所介绍的有关程序性能分析与测量的概念&#xff1a;• 确定一个程序对内存及时间的需求。• 使用操作数和执行步数来测量一个程序的时间需求。• 采用渐进符号描述复杂性&#xff0c;如 O、 、 、o 。• 利用计时函数测量一个程序的实际…

GCD 容易让人迷惑的几个小问题

来源&#xff1a;涂耀辉 链接&#xff1a;http://www.jianshu.com/p/ff444d664e51 写在开头&#xff1a; 本文旨在阐述一些大家容易产生迷惑的GCD相关内容&#xff0c;如果是需要了解一些GCD概念或者基础用法&#xff0c;可以看看这两篇文章&#xff1a;GCD 扫盲篇、巧谈GCD 。…

【贪心】【codevs】1214 线段覆盖

http://codevs.cn/problem/1214/ 我去这个题。。。wa的我都没脾气了。。。 我写while&#xff08;~scanf&#xff08;“%d”&#xff0c; &n&#xff09;&#xff09;竟然是不对的。。。 这个程序你妹多次输入是不能结束的&#xff1f;&#xff1f;&#xff1f;&#xff1f…

虚函数表剖析,网上转的,呵呵

http://www.cppblog.com/xczhang/archive/2008/01/20/41508.html C虚函数表解析(转)C中的虚函数的作用主要是实现了多态的机制。关于多态&#xff0c;简而言之就是用父类型别的指针指向其子类的实例&#xff0c;然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的…

c++语言文件流,C++ IO类、文件输入输出、string流详细讲解

新的C标准中有三分之二的内容都是描述标准库。接下来重点学习其中几种核心库设施&#xff0c;这些是应该熟练掌握的。标准库的核心是很多容器类(顺序容器和关联容器等)和一簇泛型算法(该类算法通常在顺序容器一定范围内的元素上或其他类型的序列上进行操作)。该篇主要学习IO库。…

iOS 走近商城 APP(三 WKWebView 商品规格选择框架封装)

原文链接&#xff1a;http://www.jianshu.com/p/293ee1bfe104商城 —— 由 3033 分享开篇 忽然发现最近也只有值班才能写东西了&#xff0c;中间更新了两篇其他的断了下商城相关的文章&#xff0c;仔细看了看之前觉得干货太少&#xff0c;今天写点实际的吧&#xff0c;闲说少说…

c语言如何不产生僵尸进程,第三章 九析带你处理 zombie(defunct) 进程

目录1 前言2 僵尸进程2.1 进程简介2.2 僵尸进程例子2.3 僵尸进程危害3 处理僵尸进程3.1 kill 命令3.2 kill 父进程3.3 reboot3.4 magic sysrq key 方法1 前言在 centos7 跑 Docker 和 k8s 时&#xff0c;偶尔会出现 systemctl 失效的情况&#xff0c;现象如下&#xff1a;Faile…

音视频开发学习笔记

http://www.jianshu.com/notebooks/6409162/top

VS2010 MFC中改变static字体颜色、大小、背景颜色(自定义类),及手动关联变量的方法...

在MFC的Dialog工程中生成一个CStatic的自定义类&#xff0c;类名例如为&#xff1a;CColorStatic 定义必要的变量&#xff1a; protected:COLORREF m_crText; // 字体颜色COLORREF m_crBackColor; // 背景颜色HBRUSH m_hBrush; // 画刷LOGFONT m_lf; // 字体…

android 上传字符串,Android中发送Http请求(包括文件上传、servlet接收)的实例代码...

/*** 通过拼接的方式构造请求内容&#xff0c;实现参数传输以及文件传输* param actionUrl* param params* param files* return* throws IOException*/public static String post(String actionUrl, Map params,Map files) throws IOException {String BOUNDARY java.util.UU…

JavaScript最全编码规范

转载: JavaScript最全编码规范 类型 ●基本类型&#xff1a;访问基本类型时&#xff0c;应该直接操作类型值 ●string ●number ●boolean ●null ●undefined var foo 1; var bar foo; bar 9; console.log(foo, bar); // > 1, 9 ●复合类型&#xff1a;访问复合类型时&a…

源码推荐:collectionView拖拽,仿凤凰FM iOS 局部监听键盘再也不会挡住输入框

UICollectionView拖拽移动单元以及本地保存&#xff08;上传者&#xff1a;dengqi&#xff09; UICollectionView拖拽移动单元以及本地保存&#xff0c;可以保存你上次移动的位置。 仿映客直播导航条&#xff08;上传者&#xff1a;Coolcool&#xff09; 仿映客直播自定义TabBa…

采集练习(一) php 获得全国的小学(数据来自腾讯朋友网)

注&#xff1a;发现腾讯朋友网已经改版&#xff0c;部分参数需要自己获得修改 &#xff01;&#xff01;&#xff01; 年前有个需求获得某省的小学数据&#xff0c;分析了下朋友网的小学学校发现可以获得相关数据。 如获得 湖南省郴州市宜章县的全部小学 发现网页请求的地址…

android 模板方法模式,安卓设计模式(七)模板方法模式

模板方法模式用于固定相关操作的执行流程,将具体实现延迟到子类中该系列其他文章:定义: 定义一个操作中算法的框架,而降一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤.使用场景:代码重构时,模板方法是经常被用到的,将固定部分提取到父…

solr单机版的搭建

一、solr单机版的搭建 1.运行环境 solr 需要运行在一个Servlet容器中&#xff0c;Solr4.10.3要求jdk使用1.7以上&#xff0c;Solr默认提供Jetty&#xff08;ja&#xff09;&#xff0c;本教va写的Servlet容器程使用Tocmat作为Servlet容器&#xff0c;环境如下&#xff1a; Solr…

android5.0后新特性修改标题头,Android5.0中Material Design的新特性

Material Design简介Material Design是谷歌新的设计语言&#xff0c;谷歌希望寄由此来统一各种平台上的用户体验&#xff0c;Material Design的特点是干净的排版和简单的布局&#xff0c;以此来突出内容。Material Design对排版、材质、配色、光效、间距、文字大小、交互方式、…

MFCard:易用的信用卡支付集成类库

原文链接&#xff1a;https://github.com/MobileFirstInc/MFCardMFCard&#xff1a;易用的信用卡支付集成类库。# 为开源点赞# —— 由 SwiftLanguage 分享MFCard is an awesome looking Credit Card input & validation control. Written in Swift 3. Demo Usage First St…

Castle ActiveRecord学习(四)延迟加载、分页查询、where条件

一、延迟加载 //用户发布的主题&#xff0c;一对多&#xff1b;Table&#xff1a;外键表&#xff1b;ColumnKey&#xff1a;外键&#xff1b;Lazy&#xff1a;延迟加载&#xff1b;Cascade&#xff1a;级联操作&#xff08;级联删除&#xff09;[HasMany(typeof(ThemeInfo), Ta…