iOS APP 如何改用更安全的HTTPS

本文介绍HTTPS的概念及其相较于HTTP的优势,并详细讲解如何在iOS平台上利用NSURLConnection及AFNetworking等工具实现HTTPS的支持。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么是SSL/TLS?跟HTTP和HTTPS有什么关系

打个比方:如果原来的 HTTP 是塑料水管,容易被戳破;那么如今新设计的 HTTPS 就像是在原有的塑料水管之外,再包一层金属水管。一来,原有的塑料水管照样运行;二来,用金属加固了之后,不容易被戳破。

目前,应用最广泛的是TLS 1.0,接下来是SSL 3.0(注:TSL是SSL的新别名,SSL 3.0版本之后的迭代版本被重新命名为TLS 1.0)。但是,主流浏览器都已经实现了TLS 1.2的支持。

Apple让你的HTTP采用SSL/TLS协议,就是让你从HTTP转到HTTPS

我怎么让这个图片居中,该死的


改用HTTPS有什么好处

不使用SSL/TLS的HTTP通信,就是不加密的通信!所有信息明文传播,带来了三大风险:

  • 窃听风险(eavesdropping):第三方可以获知通信内容。
  • 篡改风险(tampering):第三方可以修改通信内容。
  • 冒充风险(pretending):第三方可以冒充他人身份参与通信。

SSL/TLS协议是为了解决这三大风险而设计的,希望达到:

  • 所有信息都是加密传播,第三方无法窃听。
  • 具有校验机制,一旦被篡改,通信双方会立刻发现。
  • 配备身份证书,防止身份被冒充。

如何在iOS上实现对HTTPS的支持

首先,需要明确你使用HTTP/HTTPS的用途,因为OSX和iOS平台提供了多种API,来支持不同的用途,官方文档《Making HTTP and HTTPS Requests》 有详细的说明,而文档《HTTPS Server Trust Evaluation》则详细讲解了HTTPS验证相关知识,这里就不多说了。本文主要讲解我们最常用的NSURLConnection支持HTTPS的实现(NSURLSession的实现方法类似,只是要求授权证明的回调不一样而已),以及怎么样使用AFNetworking这个非常流行的第三方库来支持HTTPS。本文假设你对HTTP以及NSURLConnection的接口有了足够的了解。

验证证书API

相关的Api在Security Framework中,验证流程如下:

  1. 第一步,先获取需要验证的信任对象(Trust Object)。这个Trust Object在不同的应用场景下获取的方式都不一样,对于NSURLConnection来说,是从delegate方法-connection:willSendRequestForAuthenticationChallenge:回调回来的参数challenge中获取([challenge.protectionSpace serverTrust])。

  2. 使用系统默认验证方式验证Trust Object。SecTrustEvaluate会根据Trust Object的验证策略,一级一级往上,验证证书链上每一级数字签名的有效性(上一部分有讲解),从而评估证书的有效性。

  3. 如第二步验证通过了,一般的安全要求下,就可以直接验证通过,进入到下一步:使用Trust Object生成一份凭证([NSURLCredential credentialForTrust:serverTrust]),传入challenge的sender中([challenge.sender useCredential:cred forAuthenticationChallenge:challenge])处理,建立连接。

  4. 假如有更强的安全要求,可以继续对Trust Object进行更严格的验证。常用的方式是在本地导入证书,验证Trust Object与导入的证书是否匹配。更多的方法可以查看Enforcing Stricter Server Trust Evaluation,这一部分在讲解AFNetworking源码中会讲解到。

  5. 假如验证失败,取消此次Challenge-Response Authentication验证流程,拒绝连接请求。

ps: 假如是自建证书的,则会跳过第二步,使用第三部进行验证,因为自建证书的根CA的数字签名未在操作系统的信任列表中。

使用NSURLConnection支持HTTPS的实现

// Now start the connection
NSURL * httpsURL = [NSURL URLWithString:@"https://www.google.com"];

self.connection = [NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:httpsURL] delegate:self];


//回调
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {

    //1)获取trust object
    SecTrustRef trust = challenge.protectionSpace.serverTrust;

    SecTrustResultType result;


    //2)SecTrustEvaluate对trust进行验证
    OSStatus status = SecTrustEvaluate(trust, &result);

    if (status == errSecSuccess &&

        (result == kSecTrustResultProceed || result == kSecTrustResultUnspecified)) {

        //3)验证成功,生成NSURLCredential凭证cred,告知challenge的sender使用这个凭证来继续连接
        NSURLCredential *cred = [NSURLCredential credentialForTrust:trust];

        [challenge.sender useCredential:cred forAuthenticationChallenge:challenge];

    } else {

        //5)验证失败,取消这次验证流程
        [challenge.sender cancelAuthenticationChallenge:challenge];
  }
}

上面是代码是通过系统默认验证流程来验证证书的。假如我们是自建证书的呢?这样Trust Object里面服务器的证书因为不是可信任的CA签发的,所以直接使用SecTrustEvaluate进行验证是不会成功。又或者,即使服务器返回的证书是信任CA签发的,又如何确定这证书就是我们想要的特定证书?这就需要先在本地导入证书,设置成需要验证的Anchor Certificate(就是根证书),再调用SecTrustEvaluate来验证。代码如下

//先导入证书
NSString * cerPath = ...; //证书的路径

NSData * cerData = [NSData dataWithContentsOfFile:cerPath];

SecCertificateRef certificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)(cerData));

self.trustedCertificates = @[CFBridgingRelease(certificate)];

//回调
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {

    //1)获取trust object
    SecTrustRef trust = challenge.protectionSpace.serverTrust;

    SecTrustResultType result;

    //注意:这里将之前导入的证书设置成下面验证的Trust Object的anchor certificate
    SecTrustSetAnchorCertificates(trust, (__bridge CFArrayRef)self.trustedCertificates);

    //2)SecTrustEvaluate会查找前面SecTrustSetAnchorCertificates设置的证书或者系统默认提供的证书,对trust进行验证
    OSStatus status = SecTrustEvaluate(trust, &result);

    if (status == errSecSuccess &&

        (result == kSecTrustResultProceed ||

        result == kSecTrustResultUnspecified)) {

        //3)验证成功,生成NSURLCredential凭证cred,告知challenge的sender使用这个凭证来继续连接
        NSURLCredential *cred = [NSURLCredential credentialForTrust:trust];

        [challenge.sender useCredential:cred forAuthenticationChallenge:challenge];

    } else {

        //5)验证失败,取消这次验证流程
        [challenge.sender cancelAuthenticationChallenge:challenge];
  }
}

建议采用本地导入证书的方式验证证书,来保证足够的安全性。更多的验证方法,请查看官方文档《HTTPS Server Trust Evaluation》

使用AFNetworking来支持HTTPS

NSURL * url = [NSURL URLWithString:@"https://www.google.com"];

AFHTTPRequestOperationManager * requestOperationManager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:url];

dispatch_queue_t requestQueue = dispatch_create_serial_queue_for_name("kRequestCompletionQueue");

requestOperationManager.completionQueue = requestQueue;

AFSecurityPolicy * securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];

//allowInvalidCertificates 是否允许无效证书(也就是自建的证书),默认为NO
//如果是需要验证自建证书,需要设置为YES

securityPolicy.allowInvalidCertificates = YES;

//validatesDomainName 是否需要验证域名,默认为YES;
//假如证书的域名与你请求的域名不一致,需把该项设置为NO
//主要用于这种情况:客户端请求的是子域名,而证书上的是另外一个域名。因为SSL证书上的域名是独立的,假如证书上注册的域名是www.google.com,那么mail.google.com是无法验证通过的;当然,有钱可以注册通配符的域名*.google.com,但这个还是比较贵的。

securityPolicy.validatesDomainName = NO;

//validatesCertificateChain 是否验证整个证书链,默认为YES
//设置为YES,会将服务器返回的Trust Object上的证书链与本地导入的证书进行对比,这就意味着,假如你的证书链是这样的:
//GeoTrust Global CA 
//    Google Internet Authority G2
//        *.google.com

//那么,除了导入*.google.com之外,还需要导入证书链上所有的CA证书(GeoTrust Global CA, Google Internet Authority G2);
//如是自建证书的时候,可以设置为YES,增强安全性;假如是信任的CA所签发的证书,则建议关闭该验证;

securityPolicy.validatesCertificateChain = NO;

requestOperationManager.securityPolicy = securityPolicy;

这就是AFNetworking的支持HTTPS的主要配置说明,AFHTTPSessionManager与之基本一致。

关于自建证书的HTTPS的使用这里有更详细的讲解,and这里是自建证书的创建过程。

<think>嗯,用户想解决H5页面嵌入APP时在不同手机上的样式兼容性问题。首先,我需要回忆一下相关的适配方案。根据提供的引用内容,有几个关键点:比如使用rem适配、处理安全区域、fixed定位的问题,还有不同设备的像素密度问题。 首先,rem适配应该是个基础。因为不同设备的屏幕尺寸和分辨率差异大,用rem可以相对根元素字体大小来调整布局。引用[1]提到H5项目用rem适配,通过设置根元素的font-size,然后其他单位用rem,这样可以根据屏幕宽度动态调整。不过可能需要结合viewport设置,比如meta标签里的viewport设置成device-width,这样能好地响应不同设备。 然后是安全区域的问题,特别是iOS的刘海屏和底部横条。引用[4]里提到用constant和env来处理safe-area-inset,这应该是在CSS中添加内边距,避免内容被遮挡。比如在底部fixed的元素上加padding-bottom,使用constant和env函数来获取安全区域的距离。不过要注意兼容性,可能需要同时写两个height属性,一个用constant,一个用env,因为有些旧版本可能不支持env。 接下来是fixed和absolute定位的问题。不同手机对fixed的支持可能不一致,尤其是在键盘弹出时,元素的位置可能会错乱。引用[4]建议对fixed元素单独处理,增加内边距。可能需要用CSS的calc函数动态计算高度,结合安全区域的变量。另外,可能需要用JavaScript监听窗口变化,动态调整布局,比如在键盘弹出时重新计算元素位置。 还有像素密度的问题,比如Android的dp单位和px的转换。引用[1]里提到dp和px的转换公式,但H5中可能关注设备像素比(dpr)。使用viewport的initial-scale来控制缩放,或者用媒体查询根据不同的dpr来调整样式。比如,高dpr的设备可能需要细的边框,用transform: scale(0.5)来实现1物理像素的线条。 另外,不同厂商的ROM可能会有不同的默认样式,比如字体大小被调整。这时候需要重置字体,防止用户修系统字体影响布局。可以用CSS的-webkit-text-size-adjust属性设为none,或者用JavaScript检测字体大小变化,然后强制重置。 测试方面,引用[3]提供了一个网址,可以用来查看客户端信息,帮助排查问题。可能需要用真机测试,或者使用浏览器的开发者工具模拟不同设备。此外,使用Flexbox和Grid布局可以提高兼容性,因为它们能好地适应不同屏幕尺寸。 最后,可能需要处理一些特定问题,比如iOS上输入框的样式,或者键盘弹出导致的视口变化。这时候,使用特定的meta标签,或者监听resize事件来调整布局可能会有帮助。比如,在iOS中,键盘弹出可能会变视口高度,导致fixed元素位置错位,这时候可能需要改用absolute定位,或者动态调整元素的位置。 总结下来,解决方案应该包括rem适配、安全区域处理、定位元素调整、像素密度适配、字体重置、弹性布局和充分测试。同时,针对不同平台(iOS和Android)可能需要特定的处理,比如iOS安全区域和Android的status bar高度差异。</think>### H5页面嵌入APP的跨设备样式适配方案 根据引用资料和行业实践,H5页面在APP内出现样式兼容性问题主要由以下原因引起: 1. 不同设备像素密度差异(Android的dp与px转换问题)[^1] 2. iOS安全区域适配(刘海屏、Home Indicator等特殊区域)[^4] 3. 不同ROM对WebView的默认样式处理差异 4. 绝对定位元素在WebView中的异常表现[^4] #### 一、基础适配方案 1. **视口设置** ```html <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> ``` 2. **REM适配** ```javascript // 设置根字体大小为屏幕宽度的1/10 document.documentElement.style.fontSize = document.documentElement.clientWidth / 10 + 'px'; ``` 3. **安全区域适配** ```css /* 底部安全区域适配 */ .element { padding-bottom: constant(safe-area-inset-bottom); /* iOS < 11.2 */ padding-bottom: env(safe-area-inset-bottom); /* iOS >= 11.2 */ height: calc(60px + constant(safe-area-inset-bottom)); height: calc(60px + env(safe-area-inset-bottom)); } ``` #### 二、特殊问题解决方案 1. **固定定位异常** ```css .fixed-box { position: fixed; bottom: 0; /* 安卓需要增加状态栏高度适配 */ bottom: calc(0px + constant(safe-area-inset-bottom)); bottom: calc(0px + env(safe-area-inset-bottom)); } ``` 2. **1px边框问题** ```css .border-box { position: relative; } .border-box::after { content: ""; position: absolute; left: 0; bottom: 0; width: 100%; height: 1px; background: #ddd; transform: scaleY(0.5); } ``` 3. **字体大小重置** ```css body { -webkit-text-size-adjust: 100% !important; text-size-adjust: 100% !important; } ``` #### 三、调试技巧 1. 使用`https://www.bejson.com/httputil/clientinfo/`检测客户端信息[^3] 2. 安卓WebView开启调试模式: ```java WebView.setWebContentsDebuggingEnabled(true); ``` #### 四、厂商差异处理 | 设备类型 | 重点关注项 | 解决方案 | |---------|-----------|---------| | iOS | 安全区域、弹性滚动 | 使用safe-area-inset变量 | | 小米 | 字体缩放 | CSS强制设置font-size | | 华为 | 状态栏高度 | JS动态计算高度 | | OPPO | 圆角屏幕 | padding增加安全边距 |
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值